<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anubhav</title>
    <description>The latest articles on DEV Community by Anubhav (@anubhav023).</description>
    <link>https://dev.to/anubhav023</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1143176%2F8f4463c0-1c0e-4e6a-8c41-b20a27c4048a.png</url>
      <title>DEV Community: Anubhav</title>
      <link>https://dev.to/anubhav023</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anubhav023"/>
    <language>en</language>
    <item>
      <title>⚡Go Benchmarks: Does Pass by Pointer Really Make a Difference?</title>
      <dc:creator>Anubhav</dc:creator>
      <pubDate>Sun, 29 Sep 2024 07:43:07 +0000</pubDate>
      <link>https://dev.to/anubhav023/go-benchmarks-does-pass-by-pointer-really-make-a-difference-1540</link>
      <guid>https://dev.to/anubhav023/go-benchmarks-does-pass-by-pointer-really-make-a-difference-1540</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;TL;DR: Pass by Value vs. Pass by Pointer in Go&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Pass by Value&lt;/em&gt;&lt;/strong&gt;: Copies the entire struct when passed to a function, causing performance issues for large structs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Pass by Pointer&lt;/em&gt;&lt;/strong&gt;: Passes a reference (pointer) to the struct, avoiding the overhead of copying large data.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Performance Loss&lt;/strong&gt;&lt;/em&gt;: Pass by value starts to slow down noticeably when struct sizes exceed 10MB due to the memory cost of copying.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Optimal for Large Structs&lt;/strong&gt;&lt;/em&gt;: Pass by pointer is more efficient and stable, especially for data sizes greater than 10MB.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Key Insight&lt;/strong&gt;:&lt;/em&gt; For small structs, pass by value is fine. For larger structs, use pass by pointer to save time and memory.

&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've been diving deep into Go over the past week, exploring its features and performance characteristics. &lt;/p&gt;

&lt;p&gt;One of the fundamental concepts I've been examining is how Go handles data passing in &lt;strong&gt;&lt;em&gt;functions&lt;/em&gt;&lt;/strong&gt;, particularly with &lt;strong&gt;&lt;em&gt;structs&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But first, what is a even a &lt;strong&gt;&lt;em&gt;struct&lt;/em&gt;&lt;/strong&gt; ?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A struct in Go is a way to group different types of data together similar to C.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, here's a struct of 1024Mb (1Gb):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;LargeStruct&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="c"&gt;// 1024MB of data (1 GB)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// 1024 bytes = 1KB&lt;/span&gt;
&lt;span class="c"&gt;// 1024 KB = 1MB&lt;/span&gt;
&lt;span class="c"&gt;// 1024 MB = 1GB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Benchmarking: Pass by Value vs. Pass by Pointer&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The key question is: Should you pass &lt;strong&gt;&lt;em&gt;structs&lt;/em&gt;&lt;/strong&gt; by &lt;em&gt;value&lt;/em&gt; or by &lt;em&gt;pointer&lt;/em&gt; when performance matters?&lt;/p&gt;

&lt;p&gt;In Go, you can pass a struct to a function in two ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Pass by Value&lt;/em&gt;&lt;/strong&gt;: A copy of the entire struct is made, which can be inefficient for large structs as it uses additional memory and processing time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Pass by Pointer&lt;/em&gt;&lt;/strong&gt;: Instead of passing the whole struct, you pass a pointer to it. This avoids copying the struct, making it more memory-efficient, especially for large data sizes.&lt;/p&gt;

&lt;p&gt;The Benchmark Setup&lt;/p&gt;

&lt;p&gt;To truly understand the performance difference, I designed a benchmark that compares passing large structs by value and by pointer. My goal was to identify when passing by value becomes inefficient as the struct size grows.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How Did I Benchmark?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The benchmark involves gradually increasing the struct size from 1 byte to 1GB while comparing the time(nanoseconds) it takes to pass these structs by value and by pointer and increasing the struct size 2x with each iteration.&lt;/li&gt;
&lt;li&gt;For each size, I recorded the execution time using Go’s high-precision timers and exported them to a CSV for visualization.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// Run benchmarks for sizes from 1 byte to 1024MB (1 Gb)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// Increase size by 2x&lt;/span&gt;
        &lt;span class="n"&gt;durationValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;durationPointer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// Convert size to megabytes for easier readability&lt;/span&gt;
        &lt;span class="n"&gt;sizeMB&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// Write the results to CSV (size in MB, passByValue time, passByPointer time)&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%.8f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sizeMB&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;durationValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nanoseconds&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;durationPointer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nanoseconds&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="c"&gt;// Print status to monitor progress&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Completed benchmark for size: %.8f MB &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sizeMB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;generated csv file looks like this:
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4dni6sg27ymxys65hbh.png" alt="csv file of results"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benchmarking process:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Run the test&lt;/em&gt;: Each struct size was tested for both pass-by-value and pass-by-pointer methods.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Record the results&lt;/em&gt;: Execution time was recorded for each size and method.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Visualize the results&lt;/em&gt;: The results were plotted to observe how the performance scales as the struct size increases using python3 &amp;amp; matplotlib.

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Analyzing the Results&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3th7ok43mewoxbxk8sdq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3th7ok43mewoxbxk8sdq.gif" alt="benchmark graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4bjg9wqj55xlozgthm0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4bjg9wqj55xlozgthm0.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From the graph, it’s clear that passing by value starts to slow down significantly as struct sizes increase, &lt;strong&gt;especially beyond 10MB&lt;/strong&gt;. Passing by pointer remains relatively constant, with only a slight increase in execution time as struct size grows.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Pass by Value: Noticeable slowdown as size increases, indicating the inefficiency due to copying larger data.&lt;/li&gt;
&lt;li&gt;Pass by Pointer: More consistent and efficient, with minimal slowdown, making it ideal for larger structs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why Does This Happen?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The difference stems from Go's memory management. When you pass a large struct by value, the entire struct is copied, which increases memory usage and processing time. With a pointer, Go only passes the memory address, avoiding the need to copy large amounts of data.

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For small structs, pass by value is fine and might even be preferable for simplicity.&lt;/li&gt;
&lt;li&gt;For larger structs, pass by pointer is more efficient and can save significant time and memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go provides the flexibility to choose based on your performance needs, so understanding the trade-offs is crucial&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System Specifications:&lt;/strong&gt;&lt;br&gt;
The benchmarks were run on:&lt;br&gt;
&lt;strong&gt;OS&lt;/strong&gt;: Pop!_OS 22.04 LTS&lt;br&gt;
&lt;strong&gt;CPU&lt;/strong&gt;: 13th Gen Intel i5-1340P&lt;br&gt;
&lt;strong&gt;RAM&lt;/strong&gt;: 16GB&lt;br&gt;
&lt;strong&gt;GPU&lt;/strong&gt;: Intel Device a7a0&lt;/p&gt;

&lt;p&gt;This benchmark helped me understand the importance of choosing between pass-by-value and pass-by-pointer in Go functions, especially when dealing with large structs!&lt;/p&gt;




&lt;p&gt;Here's the entire benchmark's github repo: &lt;a href="https://github.com/anubhavgh023/go-pointer-vs-value-benchmark" rel="noopener noreferrer"&gt;go-pointer-vs-value-benchmark&lt;/a&gt;&lt;br&gt;
Find me on twitter(x): &lt;a href="https://twitter.com/anubhavs_twt" rel="noopener noreferrer"&gt;@anubhavs_twt&lt;/a&gt; &lt;/p&gt;

</description>
      <category>go</category>
    </item>
  </channel>
</rss>
