Passing Variables by Value vs. Passing by Pointer in Go

Introduction

Passing Variables by Value vs. Passing by Pointer in Go Introduction

Understanding the difference between passing variables by value or by pointer in Go can have a significant impact on both the performance and behavior of your programs. This decision can affect memory usage, speed, and scalability?—?factors that are crucial in performance-critical applications such as web servers or large-scale microservices. In this article, we’ll dive deep into these two approaches using code examples, benchmark results, and insights from the golang-examples repository to help you decide which method is best for your use case.

Pass by Value in?Go

Passing by value means providing a function with a copy of the variable. Modifications within the function do not affect the original variable.

Example: Passing a Struct by Value

import (
    "fmt"
)

type User struct {
    Name string
    Age  int
}

func updateUserByValue(u User) {
    u.Name = "Updated Name"
    fmt.Println("Inside updateUserByValue:", u)
}

func updateUserByValueAndReturn(u User) User {
    u.Name = "Updated Name"
    fmt.Println("Inside updateUserByValue:", u)
    return u
}

func main() {
    user := User{Name: "Alice", Age: 25}
    fmt.Println("Before updateUserByValue:", user)
    updateUserByValue(user)
    fmt.Println("After updateUserByValue:", user)

    fmt.Println("\nBefore updateUserByValueAndReturn:", user)
    userUpdated := updateUserByValueAndReturn(user)  // Passing by value (copy)
    fmt.Println("After updateUserByValue:", userUpdated) // We see the change before pass by pointer.
}        

Output:

Before updateUserByValue: {Alice 25}
Inside updateUserByValue: {Updated Name 25}
After updateUserByValue: {Alice 25}

Before updateUserByValueAndReturn: {Alice 25}
Inside updateUserByValue: {Updated Name 25}
After updateUserByValue: {Updated Name 25}        

In this scenario, the original user struct remains unchanged after the updateUserByValue function call because only a copy of it was passed to the function. However, when using updateUserByValueAndReturn, the returned struct reflects the changes made within the function, demonstrating that the original struct remains unaffected unless explicitly updated through a return value.

Pass by Pointer in?Go

Passing by pointer means providing a function with the memory address of the variable. Modifications within the function will directly affect the original variable since the function works with the actual memory location, not a copy of the variable.

Example: Passing a Struct by?Pointer

import (
    "fmt"
)

type User struct {
    Name string
    Age  int
}

func updateUserByPointer(u *User) {
    u.Name = "Updated Name"
    fmt.Println("Inside updateUserByPointer:", *u)
}

func main() {
    user := User{Name: "Alice", Age: 25}
    fmt.Println("Before updateUserByPointer:", user)
    updateUserByPointer(&user)
    fmt.Println("After updateUserByPointer:", user)
}        

Output:

Before updateUserByPointer: {Alice 25}
Inside updateUserByPointer: {Updated Name 25}
After updateUserByPointer: {Updated Name 25}        

Here, the user variable is modified directly because a pointer (&user) is passed, allowing the function to alter the original data.

Benchmarking Pass by Value vs. Pass by?Pointer

To understand the performance implications, benchmarks were conducted using different struct sizes. The results are as follows:

Struct Definitions

type SmallStruct struct {
    ID     int
    Name   string
    Price  float64
    Active bool
    Score  int
}

type MediumStruct struct {
    ID      int
    Name    string
    Price   float64
    Active  bool
    Tags    []string
    Details map[string]string
    Reviews []float64
    Age     int
    Email   string
    Phone   string
    Address string
    Rank    int
    Score   int
}

type LargeStruct struct {
    ID               int
    Name             string
    Price            float64
    Active           bool
    Tags             []string
    Details          map[string]string
    Reviews          []float64
    Address          string
    Phone            string
    Email            string
    Age              int
    Rank             int
    Score            int
    Friends          []string
    Hobbies          []string
    History          []string
    Settings         map[string]interface{}
    Data             []int
    IsValid          bool
    Location         string
    Department       string
    Position         string
    Skills           []string
    Projects         []string
    Languages        []string
    Birthdate        string
    Gender           string
    Nationality      string
    MaritalStatus    string
    Notes            string
    ExperienceYears  int
    Education        string
    Certifications   []string
    LastLogin        string
    IPAddress        string
    LastUpdated      string
    SocialMedia      []string
    Contacted        bool
    EmergencyContact string
    Permissions      []string
    Bio              string
    AvatarURL        string
    TagsMap          map[string]bool
}        

Performance Analysis

  • Small Structs: Even for small structs, passing by pointer (0.3769 ns/op) is faster than passing by value (4.728 ns/op), highlighting that even minimal copying overhead is measurable.
  • Medium Structs: The performance gap grows as the struct size increases, where passing by pointer (29.34 ns/op) becomes more efficient than passing by value (36.88 ns/op).
  • Large Structs: The difference is most pronounced with large structs, where passing by pointer (30.57 ns/op) significantly outperforms passing by value (56.23 ns/op), especially as data copies become more costly.

Key Takeaways:

  • Passing by pointer is generally faster than passing by value, especially for larger structs.
  • The efficiency gap increases as the size and complexity of the struct grows.
  • For small structs, passing by pointer still offers better performance, although the difference is smaller.

Best Practices: When to Use Pass by Value vs. Pass by?Pointer

Use pass by value:

  • When the struct is small and immutability is important.
  • When you need to ensure the original data remains untouched.
  • When the function needs to return the modified copy of the struct, keeping the original one unchanged. This is especially useful when you want to ensure that the caller’s original data remains intact while the function works with the copy (e.g., returning a modified copy after processing).

Use pass by pointer:

  • For larger structs, where copying can lead to performance degradation.
  • When you need to modify the original data directly or when performance is a critical concern.
  • When the struct is large and you don’t need to return a modified copy but rather want the changes to persist in the original object. This helps save memory and processing time by avoiding large copies.

Conclusion

The choice between passing by value and passing by pointer in Go has clear performance implications. While passing by value offers immutability and can be useful in certain scenarios, passing by pointer enhances performance by avoiding unnecessary copies. Moreover, when returning a modified value, passing by value can be helpful as it provides an explicit copy of the struct, leaving the original data unchanged. For performance-critical applications, particularly those handling larger data structures, passing by pointer is often the preferable choice.

For more in-depth analysis and the full source code, visit the golang-examples repository. Also, check out my YouTube video for additional insights and tips.

要查看或添加评论,请登录

Constantine Yachnytskyi的更多文章

社区洞察

其他会员也浏览了