I just read some short tutorials of Go and wrote a simple program sieve. Sieve uses sieve algorithm to print all the prime number that is smaller than 10000, which create a lot of go routines. I got the correct results but the program is very slow (5 seconds on my machine). I also wrote lua script and python script which implemented the same algorithm, and runs a lot faster (both are about 1 second on my machine).
Note that the purpose is to have idea of go routine's performance compared with coroutine in other languages, for example lua. The implementation is very inefficient, some comments pointed out that it's not correct way to implement Sieve of Eratosthenes. Yes, that's intentional. Some other replies pointed out that slowness is caused by print I/O. So I commented out print lines.
My question is why my sieve program implemented in Go is so slow?
Here is the code:
package main
import (
"fmt"
"sync"
)
type Sieve struct {
id int;
msg_queue chan int;
wg *sync.WaitGroup;
}
func NewSieve(id int) *Sieve {
sieve := new(Sieve)
sieve.id = id
sieve.msg_queue = make(chan int)
sieve.wg = new(sync.WaitGroup)
sieve.wg.Add(1)
return sieve
}
func (sieve *Sieve) run() {
defer sieve.wg.Done()
myprime := <-sieve.msg_queue
if myprime == 0 {
return
}
// fmt.Printf("Sieve (%d) is for prime number %d.\n", sieve.id, myprime)
next_sieve := NewSieve(sieve.id + 1)
go next_sieve.run()
for {
number := <-sieve.msg_queue
if number == 0 {
next_sieve.msg_queue <- number;
next_sieve.wg.Wait()
return
} else if number % myprime != 0 {
// fmt.Printf("id: %d, number: %d, myprime: %d, number mod myprime: %d\n", sieve.id, number, myprime, number % myprime)
next_sieve.msg_queue <- number
}
}
}
func driver() {
first := NewSieve(2)
go first.run()
for n := 2; n <= 10000; n++ {
first.msg_queue <- n
}
first.msg_queue <- 0
first.wg.Wait()
}
func main() {
driver()
}
As a comparison, here is the code of sieve.lua
function sieve(id)
local myprime = coroutine.yield()
// print(string.format("Sieve (%d) is for prime number %d", id, myprime))
local next_sieve = coroutine.create(sieve)
coroutine.resume(next_sieve, id + 1)
while true do
local number = coroutine.yield()
if number % myprime ~= 0 then
// print(string.format("id: %d, number: %d, myprime: %d, number mod myprime: %d", id, number, myprime, number % myprime))
coroutine.resume(next_sieve, number)
end
end
end
function driver()
local first = coroutine.create(sieve)
coroutine.resume(first, 2)
local n
for n = 2, 10000 do
coroutine.resume(first, n)
end
end
driver()
Meaningless microbenchmarks produce meaningless results.
You are timing print I/O.
You are incurring go routine and channel overhead for a small amount of work.
Here is a prime number sieve program in Go.
Output:
$ go version
go version devel +46be01f4e0 Sun Oct 13 01:48:30 2019 +0000 linux/amd64
$ go build sumprimes.go && time ./sumprimes
5736396
29.96µs
real 0m0.001s
user 0m0.001s
sys 0m0.000s
sumprimes.go:
package main
import (
"fmt"
"time"
)
const (
prime = 0x00
notprime = 0xFF
)
func oddPrimes(n uint64) (sieve []uint8) {
sieve = make([]uint8, (n+1)/2)
sieve[0] = notprime
p := uint64(3)
for i := p * p; i <= n; i = p * p {
for j := i; j <= n; j += 2 * p {
sieve[j/2] = notprime
}
for p += 2; sieve[p/2] == notprime; p += 2 {
}
}
return sieve
}
func sumPrimes(n uint64) uint64 {
sum := uint64(0)
if n >= 2 {
sum += 2
}
for i, p := range oddPrimes(n) {
if p == prime {
sum += 2*uint64(i) + 1
}
}
return sum
}
func main() {
start := time.Now()
var n uint64 = 10000
sum := sumPrimes(n)
fmt.Println(sum)
fmt.Println(time.Since(start))
}
Most of the time is spent in fmt.Printf.
Taking out the line:
fmt.Printf("id: %d, number: %d, myprime: %d, number mod myprime: %d\n", sieve.id, number, myprime, number%myprime)
reduces runtime from ~5.4 seconds to ~0.64 seconds on one test I ran.
Taking out the unnecessary sync.WaitGroups reduces the time a bit further, to ~0.48 seconds. See the version without sync.WaitGroup here. You're still doing a lot of channel operations, which languages with yield-value-from-coroutine operators do not need (though they have their own issues instead). This is not a good way to implement primality testing.
This question already has an answer here:
Why is this Rust slower than my similar Python? [duplicate]
(1 answer)
Closed 6 years ago.
I have a Rust program that implements a brute-force parity check for 64-bit unsigned integers:
use std::io;
use std::io::BufRead;
fn parity(mut num: u64) -> u8 {
let mut result: u8 = 0;
while num > 0 {
result = result ^ (num & 1) as u8;
num = num >> 1;
}
result
}
fn main() {
let stdin = io::stdin();
let mut num: u64;
let mut it = stdin.lock().lines();
// skip 1st line with number of test cases
it.next();
for line in it {
num = line.unwrap().parse().unwrap();
println!("{}", parity(num));
}
}
When I feed it with input file containing 1000000 unsigned integers:
$ rustc parity.rs
$ time cat input.txt | ./parity &> /dev/null
cat input.txt 0.00s user 0.02s system 0% cpu 4.178 total
./parity &> /dev/null 3.87s user 0.32s system 99% cpu 4.195 total
And here comes a surprise - the effectively same program in Go does 4x faster:
$ go build parity.go
$ time cat input.txt | ./parity &> /dev/null
cat input.txt 0.00s user 0.03s system 3% cpu 0.952 total
./parity &> /dev/null 0.63s user 0.32s system 99% cpu 0.955 total
Here's the code in Go:
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func parity(line string) uint64 {
var parity uint64
u, err := strconv.ParseUint(line, 10, 64)
if err != nil {
panic(err)
}
for u > 0 {
parity ^= u & 1
u >>= 1
}
return parity
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// skip line with number of cases
if !scanner.Scan() {
// panic if there's no number of test cases
panic("missing number of test cases")
}
for scanner.Scan() {
fmt.Println(parity(scanner.Text()))
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading standard input:", err)
}
}
Versions:
$ rustc --version
rustc 1.7.0
$ go version
go version go1.6 darwin/amd64
Sample of input file, first line contains number of input values in the file:
8
7727369244898783789
2444477357490019411
4038350233697550492
8106226119927945594
1538904728446207070
0
1
18446744073709551615
Why do the Rust and Go programs I've written have such a dramatic difference in performance? I expected Rust to be a bit faster than Go in this case. Am I doing something wrong in my Rust code?
I think you're not compiling with optimisation.
try
$ rustc -O parity.rs
Your benchmark doesn't measure the parity check. It measures input plus parity check plus output. For example, in Go, you measure scanner.Scan and strconv.ParseUint and fmt.Println as well as the parity check.
Here's a Go benchmark that just measures 1000000 parity checks.
parity_test.go:
package parity
import (
"math/rand"
"runtime"
"testing"
)
func parity(n uint64) uint64 {
var parity uint64
for n > 0 {
parity ^= n & 1
n >>= 1
}
return parity
}
func init() { runtime.GOMAXPROCS(1) }
// Benchmark 1000000 parity checks.
func BenchmarkParity1000000(b *testing.B) {
n := make([]uint64, 1000000)
for i := range n {
r := uint64(rand.Uint32())
n[i] = (r << 32) | r
}
p := parity(42)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, n := range n {
p = parity(n)
}
}
b.StopTimer()
_ = p
}
Output:
$ go test -bench=.
BenchmarkParity1000000 50 34586769 ns/op
$
I have implemented a simple function which returns the quotient and remainder when the divisor is the power of 10:
func getQuotientAndRemainder(num int64, digits uint) (int64, int64) {
divisor := int64(math.Pow(10, float64(digits)))
if num >= divisor {
return num / divisor, num % divisor
} else {
return 0, num
}
}
Just curious, except using directly / and % operators, is there any better algorithm to get the the quotient and remainder? Or only in the case when the divisor is the power of 10?
return num / divisor, num % divisor
The "algorithm" is sound and written in arguably the best way possible: expressively. If anything, this part of your code may be overly complicated:
int64(math.Pow(10, float64(digits)))
Converting to and from float64 is arguably sub-optimal. Also, 10 to the power of anything greater than 18 will overflow int64. I suggest you add a sanity check and replace the code with a multiplying loop and measure its performance.
But then: if performance is your concern, just implement it in assembly.
Obviously, you should run some Go benchmarks: Benchmarks, Package testing.
Your solution doesn't look very efficient. Try this:
package main
import "fmt"
func pow(base, exp int64) int64 {
p := int64(1)
for exp > 0 {
if exp&1 != 0 {
p *= base
}
exp >>= 1
base *= base
}
return p
}
func divPow(n, base, exp int64) (q int64, r int64) {
p := pow(base, exp)
q = n / p
r = n - q*p
return q, r
}
func main() {
fmt.Println(divPow(42, 10, 1))
fmt.Println(divPow(-42, 10, 1))
}
Output:
4 2
-4 -2
Benchmark:
BenchmarkDivPow 20000000 77.4 ns/op
BenchmarkGetQuotientAndRemainder 5000000 296 ns/op
At the moment I am implementing some sorting algorithms. As it's in the nature of algorithms, there are a lot of calls on the length of some arrays/slices using the len() method.
Now, given the following code for a (part of) the Mergesort algorithm:
for len(left) > 0 || len(right) > 0 {
if len(left) > 0 && len(right) > 0 {
if left[0] <= right[0] {
result = append(result, left[0])
left = left[1:len(left)]
} else {
result = append(result, right[0])
right = right[1:len(right)]
}
} else if len(left) > 0 {
result = append(result, left[0])
left = left[1:len(left)]
} else if len(right) > 0 {
result = append(result, right[0])
right = right[1:len(right)]
}
}
My question is: Do these multiple len() calls affect the performance of the algorithm negatively? Is it better to make a temporary variable for the length of the right and left slice? Or does the compiler does this itself?
There are two cases:
Local slice: length will be cached and there is no overhead
Global slice or passed (by reference): length cannot be cached and there is overhead
No overhead for local slices
For locally defined slices the length is cached, so there is no runtime overhead. You can see this in the assembly of the following program:
func generateSlice(x int) []int {
return make([]int, x)
}
func main() {
x := generateSlice(10)
println(len(x))
}
Compiled with go tool 6g -S test.go this yields, amongst other things, the following lines:
MOVQ "".x+40(SP),BX
MOVQ BX,(SP)
// ...
CALL ,runtime.printint(SB)
What happens here is that the first line retrieves the length of x by getting the value located 40 bytes from the beginning of x and most importantly caches this value in BX, which is then used for every occurrence of len(x). The reason for the offset is that an array has the following structure (source):
typedef struct
{ // must not move anything
uchar array[8]; // pointer to data
uchar nel[4]; // number of elements
uchar cap[4]; // allocated number of elements
} Array;
nel is what is accessed by len(). You can see this in the code generation as well.
Global and referenced slices have overhead
For shared values caching of the length is not possible since the compiler has to assume that the slice changes between calls. Therefore the compiler has to write code that accesses the length attribute directly every time. Example:
func accessLocal() int {
a := make([]int, 1000) // local
count := 0
for i := 0; i < len(a); i++ {
count += len(a)
}
return count
}
var ag = make([]int, 1000) // pseudo-code
func accessGlobal() int {
count := 0
for i := 0; i < len(ag); i++ {
count += len(ag)
}
return count
}
Comparing the assembly of both functions yields the crucial difference that as soon as the variable is global the access to the nel attribute is not cached anymore and there will be a runtime overhead:
// accessLocal
MOVQ "".a+8048(SP),SI // cache length in SI
// ...
CMPQ SI,AX // i < len(a)
// ...
MOVQ SI,BX
ADDQ CX,BX
MOVQ BX,CX // count += len(a)
// accessGlobal
MOVQ "".ag+8(SB),BX
CMPQ BX,AX // i < len(ag)
// ...
MOVQ "".ag+8(SB),BX
ADDQ CX,BX
MOVQ BX,CX // count += len(ag)
Despite the good answers you are getting, I'm getting poorer performance if calling len(a) constantly, for example in this test http://play.golang.org/p/fiP1Sy2Hfk
package main
import "testing"
func BenchmarkTest1(b *testing.B) {
a := make([]int, 1000)
for i := 0; i < b.N; i++ {
count := 0
for i := 0; i < len(a); i++ {
count += len(a)
}
}
}
func BenchmarkTest2(b *testing.B) {
a := make([]int, 1000)
for i := 0; i < b.N; i++ {
count := 0
lena := len(a)
for i := 0; i < lena; i++ {
count += lena
}
}
}
When run as go test -bench=. I get:
BenchmarkTest1 5000000 668 ns/op
BenchmarkTest2 5000000 402 ns/op
So there is clearly a penalty here, possibly because the compiler is making worse optimizations in compile-time.
Hope things got improved in the latest version of Go
go version go1.16.7 linux/amd64
goos: linux
goarch: amd64
pkg: 001_test
cpu: 11th Gen Intel(R) Core(TM) i7-1165G7 # 2.80GHz
BenchmarkTest1-8 4903609 228.8 ns/op
BenchmarkTest2-8 5280086 229.9 ns/op
In C/C++ (and many languages of that family), a common idiom to declare and initialize a variable depending on a condition uses the ternary conditional operator :
int index = val > 0 ? val : -val
Go doesn't have the conditional operator. What is the most idiomatic way to implement the same piece of code as above ? I came to the following solution, but it seems quite verbose
var index int
if val > 0 {
index = val
} else {
index = -val
}
Is there something better ?
As pointed out (and hopefully unsurprisingly), using if+else is indeed the idiomatic way to do conditionals in Go.
In addition to the full blown var+if+else block of code, though, this spelling is also used often:
index := val
if val <= 0 {
index = -val
}
and if you have a block of code that is repetitive enough, such as the equivalent of int value = a <= b ? a : b, you can create a function to hold it:
func min(a, b int) int {
if a <= b {
return a
}
return b
}
...
value := min(a, b)
The compiler will inline such simple functions, so it's fast, more clear, and shorter.
No Go doesn't have a ternary operator. Using if/else syntax is the idiomatic way.
Why does Go not have the ?: operator?
There is no ternary testing operation in Go. You may use the following to achieve the same result:
if expr {
n = trueVal
} else {
n = falseVal
}
The reason ?: is absent from Go is that the language's designers had seen the operation used too often to create impenetrably complex expressions. The if-else form, although longer, is unquestionably clearer. A language needs only one conditional control flow construct.
— Frequently Asked Questions (FAQ) - The Go Programming Language
Suppose you have the following ternary expression (in C):
int a = test ? 1 : 2;
The idiomatic approach in Go would be to simply use an if block:
var a int
if test {
a = 1
} else {
a = 2
}
However, that might not fit your requirements. In my case, I needed an inline expression for a code generation template.
I used an immediately evaluated anonymous function:
a := func() int { if test { return 1 } else { return 2 } }()
This ensures that both branches are not evaluated as well.
The map ternary is easy to read without parentheses:
c := map[bool]int{true: 1, false: 0} [5 > 4]
Foreword: Without arguing that if else is the way to go, we can still play with and find pleasure in language-enabled constructs.
Go 1.18 generics update: Go 1.18 adds generics support. It is now possible to create a generic If() function like this. Note: This is available in github.com/icza/gog, as gog.If() (disclosure: I'm the author).
func If[T any](cond bool, vtrue, vfalse T) T {
if cond {
return vtrue
}
return vfalse
}
Which you can use like this:
min := If(i > 0, i, 0)
The pre-1.18 answer follows:
The following If construct is available in my github.com/icza/gox library with lots of other methods, being the gox.If type.
Go allows to attach methods to any user-defined types, including primitive types such as bool. We can create a custom type having bool as its underlying type, and then with a simple type conversion on the condition, we have access to its methods. Methods that receive and select from the operands.
Something like this:
type If bool
func (c If) Int(a, b int) int {
if c {
return a
}
return b
}
How can we use it?
i := If(condition).Int(val1, val2) // Short variable declaration, i is of type int
|-----------| \
type conversion \---method call
For example a ternary doing max():
i := If(a > b).Int(a, b)
A ternary doing abs():
i := If(a >= 0).Int(a, -a)
This looks cool, it's simple, elegant, and efficient (it's also eligible for inlining).
One downside compared to a "real" ternary operator: it always evaluates all operands.
To achieve deferred and only-if-needed evaluation, the only option is to use functions (either declared functions or methods, or function literals), which are only called when / if needed:
func (c If) Fint(fa, fb func() int) int {
if c {
return fa()
}
return fb()
}
Using it: Let's assume we have these functions to calculate a and b:
func calca() int { return 3 }
func calcb() int { return 4 }
Then:
i := If(someCondition).Fint(calca, calcb)
For example, the condition being current year > 2020:
i := If(time.Now().Year() > 2020).Fint(calca, calcb)
If we want to use function literals:
i := If(time.Now().Year() > 2020).Fint(
func() int { return 3 },
func() int { return 4 },
)
Final note: if you would have functions with different signatures, you could not use them here. In that case you may use a function literal with matching signature to make them still applicable.
For example if calca() and calcb() would have parameters too (besides the return value):
func calca2(x int) int { return 3 }
func calcb2(x int) int { return 4 }
This is how you could use them:
i := If(time.Now().Year() > 2020).Fint(
func() int { return calca2(0) },
func() int { return calcb2(0) },
)
Try these examples on the Go Playground.
func Ternary(statement bool, a, b interface{}) interface{} {
if statement {
return a
}
return b
}
func Abs(n int) int {
return Ternary(n >= 0, n, -n).(int)
}
This will not outperform if/else and requires cast but works. FYI:
BenchmarkAbsTernary-8 100000000 18.8 ns/op
BenchmarkAbsIfElse-8 2000000000 0.27 ns/op
If all your branches make side-effects or are computationally expensive the following would a semantically-preserving refactoring:
index := func() int {
if val > 0 {
return printPositiveAndReturn(val)
} else {
return slowlyReturn(-val) // or slowlyNegate(val)
}
}(); # exactly one branch will be evaluated
with normally no overhead (inlined) and, most importantly, without cluttering your namespace with a helper functions that are only used once (which hampers readability and maintenance). Live Example
Note if you were to naively apply Gustavo's approach:
index := printPositiveAndReturn(val);
if val <= 0 {
index = slowlyReturn(-val); // or slowlyNegate(val)
}
you'd get a program with a different behavior; in case val <= 0 program would print a non-positive value while it should not! (Analogously, if you reversed the branches, you would introduce overhead by calling a slow function unnecessarily.)
As others have noted, golang does not have a ternary operator or any equivalent. This is a deliberate decision thought to improve readability.
This recently lead me to a scenario where constructing a bit-mask in a very efficient manner became hard to read when written idiomatically, or very inefficient when encapsulated as a function, or both, as the code produces branches:
package lib
func maskIfTrue(mask uint64, predicate bool) uint64 {
if predicate {
return mask
}
return 0
}
producing:
text "".maskIfTrue(SB), NOSPLIT|ABIInternal, $0-24
funcdata $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
funcdata $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
movblzx "".predicate+16(SP), AX
testb AL, AL
jeq maskIfTrue_pc20
movq "".mask+8(SP), AX
movq AX, "".~r2+24(SP)
ret
maskIfTrue_pc20:
movq $0, "".~r2+24(SP)
ret
What I learned from this was to leverage a little more Go; using a named result in the function (result int) saves me a line declaring it in the function (and you can do the same with captures), but the compiler also recognizes this idiom (only assign a value IF) and replaces it - if possible - with a conditional instruction.
func zeroOrOne(predicate bool) (result int) {
if predicate {
result = 1
}
return
}
producing a branch-free result:
movblzx "".predicate+8(SP), AX
movq AX, "".result+16(SP)
ret
which go then freely inlines.
package lib
func zeroOrOne(predicate bool) (result int) {
if predicate {
result = 1
}
return
}
type Vendor1 struct {
Property1 int
Property2 float32
Property3 bool
}
// Vendor2 bit positions.
const (
Property1Bit = 2
Property2Bit = 3
Property3Bit = 5
)
func Convert1To2(v1 Vendor1) (result int) {
result |= zeroOrOne(v1.Property1 == 1) << Property1Bit
result |= zeroOrOne(v1.Property2 < 0.0) << Property2Bit
result |= zeroOrOne(v1.Property3) << Property3Bit
return
}
produces https://go.godbolt.org/z/eKbK17
movq "".v1+8(SP), AX
cmpq AX, $1
seteq AL
xorps X0, X0
movss "".v1+16(SP), X1
ucomiss X1, X0
sethi CL
movblzx AL, AX
shlq $2, AX
movblzx CL, CX
shlq $3, CX
orq CX, AX
movblzx "".v1+20(SP), CX
shlq $5, CX
orq AX, CX
movq CX, "".result+24(SP)
ret
eold's answer is interesting and creative, perhaps even clever.
However, it would be recommended to instead do:
var index int
if val > 0 {
index = printPositiveAndReturn(val)
} else {
index = slowlyReturn(-val) // or slowlyNegate(val)
}
Yes, they both compile down to essentially the same assembly, however this code is much more legible than calling an anonymous function just to return a value that could have been written to the variable in the first place.
Basically, simple and clear code is better than creative code.
Additionally, any code using a map literal is not a good idea, because maps are not lightweight at all in Go. Since Go 1.3, random iteration order for small maps is guaranteed, and to enforce this, it's gotten quite a bit less efficient memory-wise for small maps.
As a result, making and removing numerous small maps is both space-consuming and time-consuming. I had a piece of code that used a small map (two or three keys, are likely, but common use case was only one entry) But the code was dog slow. We're talking at least 3 orders of magnitude slower than the same code rewritten to use a dual slice key[index]=>data[index] map. And likely was more. As some operations that were previously taking a couple of minutes to run, started completing in milliseconds.\
One-liners, though shunned by the creators, have their place.
This one solves the lazy evaluation problem by letting you, optionally, pass functions to be evaluated if necessary:
func FullTernary(e bool, a, b interface{}) interface{} {
if e {
if reflect.TypeOf(a).Kind() == reflect.Func {
return a.(func() interface{})()
}
return a
}
if reflect.TypeOf(b).Kind() == reflect.Func {
return b.(func() interface{})()
}
return b
}
func demo() {
a := "hello"
b := func() interface{} { return a + " world" }
c := func() interface{} { return func() string { return "bye" } }
fmt.Println(FullTernary(true, a, b).(string)) // cast shown, but not required
fmt.Println(FullTernary(false, a, b))
fmt.Println(FullTernary(true, b, a))
fmt.Println(FullTernary(false, b, a))
fmt.Println(FullTernary(true, c, nil).(func() string)())
}
Output
hello
hello world
hello world
hello
bye
Functions passed in must return an interface{} to satisfy the internal cast operation.
Depending on the context, you might choose to cast the output to a specific type.
If you wanted to return a function from this, you would need to wrap it as shown with c.
The standalone solution here is also nice, but could be less clear for some uses.
Now with the release of go1.18 generics, it's very easy to do it with a generic function like this, and it is reusable through your whole app
package main
import (
"fmt"
)
func Ternary[T any](condition bool, If, Else T) T {
if condition {
return If
}
return Else
}
func main() {
fmt.Println(Ternary(1 < 2, "yes", "no")) // yes
fmt.Println(Ternary(1 < 2, 1, 0)) // 1
fmt.Println(Ternary[bool](1 < 2, true, false)) // true
}
be aware if you use it in this case it will crash.
in this case, just use an if statement,
(because you passing into the function a nil pointer VS an if statement is not calling that section if it is false)
var a *string
fmt.Println(Ternary(a != nil, *a, "some thing else"))
the solution call it with a function, so it will not be excuted if it's false
func TernaryPointer[T any](condition bool, If, Else func() T) T {
if condition {
return If()
}
return Else()
}
var pString *string
fmt.Println(TernaryPointer(
pString != nil, // condition
func() string { return *pString }, // true
func() string { return "new data" }, // false
))
but in this case, I think a regular if statement is cleaner (except if go adds arrow functions in the future)
playground
give credit for this answer he already answered it
I have compiled some items and compared the speed.
/*
go test ternary_op_test.go -v -bench="^BenchmarkTernaryOperator" -run=none -benchmem
*/
package _test
import (
"testing"
)
func BenchmarkTernaryOperatorIfElse(b *testing.B) {
for i := 0; i < b.N; i++ {
if i%2 == 0 {
_ = i
} else {
_ = -i
}
}
}
// https://stackoverflow.com/a/45886594/9935654
func Ternary(statement bool, a, b interface{}) interface{} {
if statement {
return a
}
return b
}
func BenchmarkTernaryOperatorTernaryFunc(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Ternary(i%2 == 0, i, -i).(int)
}
}
// https://stackoverflow.com/a/34636594/9935654
func BenchmarkTernaryOperatorWithFunc(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = func() int {
if i%2 == 0 {
return i
} else {
return -i
}
}
}
}
// https://stackoverflow.com/a/31483763/9935654
func BenchmarkTernaryOperatorMap(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = map[bool]int{true: i, false: -i}[i%2 == 0]
}
}
output
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i7-8565U CPU # 1.80GHz
BenchmarkTernaryOperatorIfElse
BenchmarkTernaryOperatorIfElse-8 1000000000 0.4460 ns/op 0 B/op 0 allocs/op
BenchmarkTernaryOperatorTernaryFunc
BenchmarkTernaryOperatorTernaryFunc-8 1000000000 0.3602 ns/op 0 B/op 0 allocs/op
BenchmarkTernaryOperatorWithFunc
BenchmarkTernaryOperatorWithFunc-8 659517496 1.642 ns/op 0 B/op 0 allocs/op
BenchmarkTernaryOperatorMap
BenchmarkTernaryOperatorMap-8 13429532 82.48 ns/op 0 B/op 0 allocs/op
PASS
ok command-line-arguments 4.365s
One more suggestion for the idiomatic approach in Go of ternary operator:
package main
import (
"fmt"
)
func main() {
val := -5
index := func (test bool, n, d int) int {
if test {
return n
}
return d
}(val > 0, val, -val)
fmt.Println(index)
}
Go Playground
I was playing with a solution that doesn't use the three arguments function.
Don't take me wrong, the three arguments solution works great but personally i like to name things explicitly.
What i'd love is an explicit interface like that:
When(<condition>).Then(<true value>).Else(<false value>)
I implemented this like that:
type Else[T any] interface {
ElseDo(fn func() T) T
Else(value T) T
}
type Then[T any] interface {
ThenDo(fn func() T) Else[T]
Then(value T) Else[T]
}
type Condition[T any] struct {
condition bool
thenValue T
thenFn func() T
}
func When[T any](condition bool) Then[T] {
return &Condition[T]{condition: condition}
}
func (c *Condition[T]) ThenDo(fn func() T) Else[T] {
c.thenFn = fn
return c
}
func (c *Condition[T]) Then(value T) Else[T] {
c.thenValue = value
return c
}
func (c *Condition[T]) ElseDo(fn func() T) T {
if c.condition {
return c.then()
}
return fn()
}
func (c *Condition[T]) Else(value T) T {
if c.condition {
return c.then()
}
return value
}
func (c *Condition[T]) then() T {
if c.thenFn != nil {
return c.thenFn()
}
return c.thenValue
}
Usage:
When[int](something == "expectedValue").Then(0).Else(1)
When[int](value > 0).Then(value).Else(1)
When[int](value > 0).ThenDo(func()int {return value * 4}).Else(1)
When[string](boolean == true).Then("it is true").Else("it is false")
Unfortunately i didn't find a way to get rid of the explicit type when calling the When function. The type is not automatically inferred by the return types of Then/Else 🤷♂️