2
\$\begingroup\$

Just for curiosity I have done some benchmark for testing the best performance between switch, binary-search, and if-else statement. here is the code :

package switchStatement

type Badge struct {
    score int
    badge string
}

func initiateBadges() []Badge {
    badge := []Badge{
        {score: 4501, badge: "gold-1.gif"},
        {score: 10001, badge: "gold-2.gif"},
        {score: 15001, badge: "gold-3.gif"},
        {score: 30001, badge: "gold-4.gif"},
        {score: 45001, badge: "gold-5.gif"},
        {score: 50001, badge: "diamond-1.gif"},
        {score: 100001, badge: "diamond-2.gif"},
        {score: 150001, badge: "diamond-3.gif"},
        {score: 200001, badge: "diamond-4.gif"},
        {score: 500001, badge: "diamond-5.gif"},
    }

    return badge
}

func GetReputationBadgeImprove(score int) string {

    badges := initiateBadges()

    //search score using binary search
    index := BinarySearchBadge(badges, score)
    if index == -1 {
        return "badges-off.jpg"
    }
    return badges[index].badge
}

func BinarySearchBadge(badges []Badge, score int) int {
    low := 0
    high := len(badges) - 1
    middle := (high + low) / 2

    for low <= high {
        //checking to avoid index out of range
        if middle == len(badges)-1 {
            return middle
        }

        if badges[middle].score <= score && badges[middle+1].score > score {
            return middle
        } else if badges[middle].score >= score {
            high = middle - 1

        } else {
            low = middle + 1
        }

        middle = (high + low) / 2
    }

    return -1
}

func GetReputationBadgeImproveSwitchCase(score int) string {
    switch {
    case score >= 500001:
        return "diamond-5.gif"
    case score >= 200001:
        return "diamond-4.gif"
    case score >= 150001:
        return "diamond-3.gif"
    case score >= 100001:
        return "diamond-2.gif"
    case score >= 50001:
        return "diamond-1.gif"
    case score >= 45001:
        return "gold-5.gif"
    case score >= 30001:
        return "gold-4.gif"
    case score >= 15001:
        return "gold-3.gif"
    case score >= 10001:
        return "gold-2.gif"
    case score >= 4501:
        return "gold-1.gif"
    default:
        return "badges-off.jpg"
    }
}

func GetReputationBadge(score int) string {

    badge := "badges-off.jpg"
    if score >= 50001 { // Diamond
        badge = "diamond-1.gif"
        if score >= 500001 {
            badge = "diamond-5.gif"
        } else if score >= 200001 {
            badge = "diamond-4.gif"
        } else if score >= 150001 {
            badge = "diamond-3.gif"
        } else if score >= 100001 {
            badge = "diamond-2.gif"
        }
    } else if score >= 4501 { // Gold
        badge = "gold-1.gif"
        if score >= 45001 {
            badge = "gold-5.gif"
        } else if score >= 30001 {
            badge = "gold-4.gif"
        } else if score >= 15001 {
            badge = "gold-3.gif"
        } else if score >= 10001 {
            badge = "gold-4.gif"
        }
    }

    return badge
}

And I write some test and benchmark :

package switchStatement

import (
    "testing"
)

func TestGetReputationBadge(t *testing.T) {
    result := GetReputationBadgeImproveSwitchCase(10)
    if result != "badges-off.jpg" {
        t.Error("Expected badges-off.jpg got = ", result)
    }
}

func TestGetReputationImprove(testing *testing.T) {

    //using table drive test or tt
    var tt = []struct {
        input    int
        expected string
    }{
        {10, "badges-off.jpg"},
        {100, "badges-off.jpg"},
        {4501, "gold-1.gif"},
        {4561, "gold-1.gif"},
        {13001, "gold-2.gif"},
        {104001, "diamond-2.gif"},
        {594401, "diamond-5.gif"},
    }

    for _, t := range tt {
        actual := GetReputationBadgeImprove(t.input)
        if actual != t.expected {
            testing.Errorf("Test failed expected : %s, actual : %s", t.expected, actual)
        }
    }
}

//binary search
func benchmarkGetReputation(input int, b *testing.B) {
    for n := 0; n < b.N; n++ {
        GetReputationBadgeImprove(input)
    }
}

//switch
func benchmarkGetReputationSwitch(input int, b *testing.B) {
    for n := 0; n < b.N; n++ {
        GetReputationBadgeImproveSwitchCase(input)
    }
}

//if-else statement
func benchmarkGetReputationIf(input int, b *testing.B) {
    for n := 0; n < b.N; n++ {
        GetReputationBadge(input)
    }
}

//benchmark using binary search
func BenchmarkGetReputation1(b *testing.B) { benchmarkGetReputation(10, b) }
func BenchmarkGetReputation2(b *testing.B) { benchmarkGetReputation(4555, b) }
func BenchmarkGetReputation3(b *testing.B) { benchmarkGetReputation(14555, b) }
func BenchmarkGetReputation4(b *testing.B) { benchmarkGetReputation(504001, b) }

//benchmark using switch
func BenchmarkGetReputation1Switch(b *testing.B) { benchmarkGetReputationSwitch(10, b) }
func BenchmarkGetReputation2Switch(b *testing.B) { benchmarkGetReputationSwitch(4555, b) }
func BenchmarkGetReputation3Switch(b *testing.B) { benchmarkGetReputationSwitch(14555, b) }
func BenchmarkGetReputation4Switch(b *testing.B) { benchmarkGetReputationSwitch(504001, b) }

//benchmark if-else statemen
func BenchmarkGetReputation1If(b *testing.B) { benchmarkGetReputation(10, b) }
func BenchmarkGetReputation2If(b *testing.B) { benchmarkGetReputation(4555, b) }
func BenchmarkGetReputation3If(b *testing.B) { benchmarkGetReputation(14555, b) }
func BenchmarkGetReputation4If(b *testing.B) { benchmarkGetReputation(504001, b) }

This is the result from my benchmark test :

BenchmarkGetReputation1-4           20000000           114 ns/op
BenchmarkGetReputation2-4           10000000           114 ns/op
BenchmarkGetReputation3-4           10000000           113 ns/op
BenchmarkGetReputation4-4           10000000           118 ns/op
BenchmarkGetReputation1Switch-4     2000000000           1.80 ns/op
BenchmarkGetReputation2Switch-4     2000000000           1.80 ns/op
BenchmarkGetReputation3Switch-4     1000000000           2.16 ns/op
BenchmarkGetReputation4Switch-4     2000000000           0.36 ns/op
BenchmarkGetReputation1If-4         10000000           137 ns/op
BenchmarkGetReputation2If-4         10000000           129 ns/op
BenchmarkGetReputation3If-4         10000000           112 ns/op
BenchmarkGetReputation4If-4         10000000           118 ns/op
PASS
ok      github.com/hello/switchStatement    22.554s

from there I can conclude that switch is the winner here. Any improvement for the test or the code to get a better result?

Note : you can find the code here

\$\endgroup\$
2
\$\begingroup\$

In GetReputationBadge, you're needlessly allocating strings by modifying the badge variable. It would be a fairer benchmark to write it in the following way:

func GetReputationBadge(score int) string {
    if score >= 500001 {
        return "diamond-5.gif"
    }
    if score >= 200001 {
        return "diamond-4.gif"
    }
    // etc.
    if score >= 4501 {
        return "gold-1.gif"
    }
    return "badges-off.jpg"
}

(That said, I'm not convinced it matters — the compiler should probably optimize this anyway.)

I tried it and was very surprised to see that the benchmarks were still very poor. Turns out, you called the wrong function in your benchmark :-) BenchmarkGetReputation1If calls benchmarkGetReputation insead of benchmarkGetReputationIf. Retrying it with this fixed leads to the expected result:

BenchmarkGetReputation1-4           10000000       188 ns/op
BenchmarkGetReputation2-4           10000000       182 ns/op
BenchmarkGetReputation3-4           10000000       179 ns/op
BenchmarkGetReputation4-4           10000000       192 ns/op
BenchmarkGetReputation1Switch-4     300000000         4.55 ns/op
BenchmarkGetReputation2Switch-4     300000000         4.32 ns/op
BenchmarkGetReputation3Switch-4     300000000         4.63 ns/op
BenchmarkGetReputation4Switch-4     2000000000         0.84 ns/op
BenchmarkGetReputation1If-4         300000000         4.21 ns/op
BenchmarkGetReputation2If-4         300000000         4.19 ns/op
BenchmarkGetReputation3If-4         300000000         4.74 ns/op
BenchmarkGetReputation4If-4         2000000000         0.88 ns/op

Note that this benchmark is still pretty flawed: you're only testing it with a couple of values, so depending on which they are, the order of the if/else statements are going to matter. You should use much more values, picked among a reasonable distribution instead.

\$\endgroup\$
  • \$\begingroup\$ thank you for correcting the if-else statement. so it turns out the switch and if-else here is the same in terms of performance? \$\endgroup\$ – Gujarat Santana Dec 10 '16 at 15:17
  • 1
    \$\begingroup\$ Yes; which I don't find surprising — they're pretty much the same thing with some syntaxic sugar. Ashwini's mentions that the switch is doing a binary search; but I don't expect this to change benchmarks significantly for switch statements with very simple conditions and less than a few dozen cases. \$\endgroup\$ – Ted Dec 10 '16 at 15:24
  • \$\begingroup\$ alright I will give this a try and let you know, I will add some more conditions to it and see the difference. For example above it only mention for gold badges. I will try with silver, bronze, etc \$\endgroup\$ – Gujarat Santana Dec 10 '16 at 15:28
  • \$\begingroup\$ your benchmark and my benchmark is kinda different, I've got the same result as before when using if-else just like your suggestion. I will post the code in my github \$\endgroup\$ – Gujarat Santana Dec 10 '16 at 22:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.