A Commonly Unaddressed Issue in C++ and Golang Comparisons

I just wanted to address a seldom mentioned nuance in the comparison of the Go and C++ programming languages. Pretty much every website or forum you go to will mention that C++ is usually at least 50% faster than go. What they don't mention is the amount of extra effort that went into optimizing the C++ code over the Go code. Recent experiences have shown me that the final level of optimization that makes C++ actually faster than Go are just not worth it to a normal developer.

Background

To perhaps give just a little more backing to my humble opinion I thought I would briefly mention my history with Go, and especially C++ (being that an inexperienced C++ user can very easily write bad code).

C++ was the first programming language I ever learned years ago. I still remember following Bucky's C++ tutorials on Youtube (https://www.youtube.com/playlist?list=PLAE85DE8440AA6B83). Thus, it has always had a soft spot in my heart. Several years later it was the first language I was taught in college (Crazy right!? They have since switched the curriculum to Python). 

Thought later college classes didn't focus on it as much. I still dabbled in it and kept up my skills. In fact, I got an internship writing C++ 17 for a self-driving vehicle company. So, I know at least a small part of the horror that is legacy/production C++ code.

While I claim to be no C++ expert, I hope that I can at least paint myself as someone who is not oblivious to the philosophy and coding styles used in C++.

Go on the other hand I learned just about two years ago, and I immediately fell in love with it. I got myself a copy of Learning Go (https://www.amazon.com/Learning-Go-Idiomatic-Real-World-Programming/dp/1492077216) and read the whole thing in a week.

I've always loved the no-overhead, manual memory management, and feature richness of C++. And I love the simplicity of Go, with garbage collection and native binaries.

My Latest C++ Adventure

I recently got bit by the C++ bug and was in the mood to write something blazingly fast and efficient. I was eager to use a lot of the new features in C++ 23 like modules and formatters. And I landed on rewriting my chess library from Go in C++ (https://github.com/brighamskarda/chess).

The thought was that utilizing C++ I could get some nice performance gains in the move generation department.

Of course, there were some bumps along the way:
  • C++ modules are still extremely finicky, and I ended up abandoning them.
  • C++ tooling sucks (thank goodness CLion now has a community Edition).
  • C++ documentation was written by math PhDs instead of humans.
It is a general expectation that writing C++ code will take a good bit longer than Go, and I was just fine with that. After all, in the end I would be able to take advantage of the amazing compilers for C++ that are supposed to give massive speed increases over the Go compiler.

How C++ Stacked Up Against Go?

Now imagine my surprise when I ran a move generation performance test (shortened to perft, https://www.chessprogramming.org/Perft) on my C++ chess library, and it ran slower than my Go code.

It didn't make sense, even when I optimized my builds for speed:
  • -O3 optimizations
  • Link Time Optimization
  • Profile Guided Optimization
  • Using MinGW (GCC) and the MSVC compilers.
I've included the code here so you can see and compare it to my Go code (https://github.com/brighamskarda/my-little-cpp-chess-adventures). 

Once again, I claim to be no C++ expert, and I have no doubt that my C++ code could be made to run faster than my Go code.

But that's not the purpose of this article. The purpose of this article is to show that average code written in Go is not necessarily slower than average code written in C++. Here are my results even after spending hours optimizing my C++ code with the help of Gemini.

Average time to calculate Perft 6 across 10 runs:
  • CPP (MSVC) - 9.27699s
  • CPP (MinGW) - 15.64184s
  • Go - 7.4765s

Well, that is rather frustrating and surprising considering how closely my CPP code follows the Go code. The bitboard move generation is basically copied from my Go library. 

Conclusion

I suppose the main point I want to make with this article is that compiler optimizations and manual memory management are not magic performance gains. These results lead me to believe that the Go compiler isn't all that inefficient, and especially in cases where heap allocations are kept to a minimum Go really isn't that much slower than C++.

It seems that unless you have a very deep understanding of how C++ works, there is a decent chance that you can write faster code in GO just by avoiding the footguns in C++. I definitely shot myself in the foot somewhere in my C++ code, but the fact that it isn't obvious and I was just using common utilities in the standard library is perhaps a sign that C++ doesn't give free performance gains unless you are willing to really get into it.