Skip to content

Commit

Permalink
Introduce a more efficient Log2 for the uint64 type (#691)
Browse files Browse the repository at this point in the history
The current practice of converting the uint64 to a float64 and passing
it to math.Log2() and then converting the result back to an int is
about 18x slower on my M3 MacPro

❯ go test . --fuzz=Fuzz --fuzztime 45s
fuzz: elapsed: 0s, gathering baseline coverage: 0/5 completed
fuzz: elapsed: 0s, gathering baseline coverage: 5/5 completed, now fuzzing with 12 workers
fuzz: elapsed: 3s, execs: 748073 (249310/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 6s, execs: 1529140 (260330/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 9s, execs: 2306601 (259140/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 12s, execs: 3091550 (261720/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 15s, execs: 3879652 (262634/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 18s, execs: 4659101 (259862/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 21s, execs: 5450467 (263818/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 24s, execs: 6232258 (260567/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 27s, execs: 7016319 (261325/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 30s, execs: 7794643 (259510/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 33s, execs: 8578148 (261116/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 36s, execs: 9360677 (260807/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 39s, execs: 10143019 (260780/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 42s, execs: 10931666 (262931/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 45s, execs: 11717753 (262059/sec), new interesting: 1 (total: 6)
fuzz: elapsed: 45s, execs: 11717753 (0/sec), new interesting: 1 (total: 6)
PASS
ok  	github.com/OffchainLabs/bold/math	45.854s

❯ go test -bench=. --benchtime=20s
goos: darwin
goarch: arm64
pkg: github.com/OffchainLabs/bold/math
BenchmarkUnsingedIntegerLog2-12    	1000000000	         0.2990 ns/op
BenchmarkMathLog2-12               	1000000000	         5.414 ns/op
PASS
ok  	github.com/OffchainLabs/bold/math	6.991s
  • Loading branch information
eljobe authored Sep 27, 2024
1 parent 82f06a5 commit 8b55e15
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 1 deletion.
10 changes: 9 additions & 1 deletion math/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "math",
srcs = ["math.go"],
srcs = ["math.go", "intlog2.go"],
importpath = "github.com/OffchainLabs/bold/math",
visibility = ["//visibility:public"],
)
Expand All @@ -14,3 +14,11 @@ go_test(
deps = ["@com_github_stretchr_testify//require"],
size = "small",
)

go_test(
name = "intlog2_test",
srcs = ["intlog2_test.go"],
embed = [":math"],
deps = ["@com_github_stretchr_testify//require"],
size = "small",
)
11 changes: 11 additions & 0 deletions math/intlog2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package math

import "math/bits"

// Log2 returns the integer logarithm base 2 of u (rounded down).
func Log2(u uint64) int {
if u == 0 {
panic("log2 undefined for non-positive values")
}
return bits.Len64(u) - 1
}
76 changes: 76 additions & 0 deletions math/intlog2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2024, Offchain Labs, Inc.
// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE.md

package math

import (
"fmt"
"math"
"testing"

"github.com/stretchr/testify/require"
)

func TestUnsingedIntegerLog2(t *testing.T) {
type log2TestCase struct {
input uint64
expected int
}

testCases := []log2TestCase{
{input: 1, expected: 0},
{input: 2, expected: 1},
{input: 4, expected: 2},
{input: 6, expected: 2},
{input: 8, expected: 3},
{input: 24601, expected: 14},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("%d", tc.input), func(t *testing.T) {
res := Log2(tc.input)
require.Equal(t, tc.expected, res)
})
}
}

func TestUnsingedIntegerLog2PanicsOnZero(t *testing.T) {
require.Panics(t, func() {
Log2(0)
})
}

func FuzzUnsingedIntegerLog2(f *testing.F) {
testcases := []uint64{0, 2, 4, 6, 8}
for _, tc := range testcases {
f.Add(tc)
}
f.Fuzz(func(t *testing.T, input uint64) {
if input == 0 {
require.Panics(t, func() {
Log2(input)
})
t.Skip()
}
r := Log2(input)
fr := math.Log2(float64(input))
require.Equal(t, int(math.Floor(fr)), r)
})
}

var benchResult int

func BenchmarkUnsingedIntegerLog2(b *testing.B) {
var r int
for i := 1; i < b.N; i++ {
r = Log2(uint64(i))
}
benchResult = r
}

func BenchmarkMathLog2(b *testing.B) {
var r int
for i := 1; i < b.N; i++ {
r = int(math.Log2(float64(i)))
}
benchResult = r
}

0 comments on commit 8b55e15

Please sign in to comment.