Skip to content

Commit

Permalink
conversion: introduce alloc-free IntoBig method (#177)
Browse files Browse the repository at this point in the history
`ToBig` does 2x32 byte allocations. Once to create a new `big.Int` and a second time to seed the contents of the `big.Int`. This change adds `IntoBig` which takes an input `big.Int`, and, if possible, it will use the given `big.Int` buffer for conversion to avoid reallocation.

If the big.Int is nil, it will be allocated (32 byte). If it's not large enough, it's data space will be allocated (32 byte).
  • Loading branch information
karalabe committed Jul 25, 2024
1 parent 75a5209 commit 9fb9e97
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 11 deletions.
1 change: 0 additions & 1 deletion benchmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,6 @@ func BenchmarkHashTreeRoot(b *testing.B) {
}

func BenchmarkSet(bench *testing.B) {

benchmarkUint256 := func(bench *testing.B) {
a := new(Int).SetBytes(hex2Bytes("f123456789abcdeffedcba9876543210f2f3f4f5f6f7f8f9fff3f4f5f6f7f8f9"))

Expand Down
61 changes: 51 additions & 10 deletions conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,62 @@ func (z *Int) ToBig() *big.Int {
if z == nil {
return nil
}
b := new(big.Int)
var b *big.Int
z.IntoBig(&b)
return b
}

// IntoBig sets a provided big.Int to the value of z.
// Sets `nil` if z is nil (thus the double pointer).
func (z *Int) IntoBig(b **big.Int) {
if z == nil {
*b = nil
return
}
if *b == nil {
*b = new(big.Int)
}
switch maxWords { // Compile-time check.
case 4: // 64-bit architectures.
words := [4]big.Word{big.Word(z[0]), big.Word(z[1]), big.Word(z[2]), big.Word(z[3])}
b.SetBits(words[:])
if words := (*b).Bits(); cap(words) >= 4 {
// Enough underlying space to set all the uint256 data
words = words[:4]

words[0] = big.Word(z[0])
words[1] = big.Word(z[1])
words[2] = big.Word(z[2])
words[3] = big.Word(z[3])

// Feed it back to normalize (up or down within the big.Int)
(*b).SetBits(words)
} else {
// Not enough space to set all the words, have to allocate
words := [4]big.Word{big.Word(z[0]), big.Word(z[1]), big.Word(z[2]), big.Word(z[3])}
(*b).SetBits(words[:])
}
case 8: // 32-bit architectures.
words := [8]big.Word{
big.Word(z[0]), big.Word(z[0] >> 32),
big.Word(z[1]), big.Word(z[1] >> 32),
big.Word(z[2]), big.Word(z[2] >> 32),
big.Word(z[3]), big.Word(z[3] >> 32),
if words := (*b).Bits(); cap(words) >= 8 {
// Enough underlying space to set all the uint256 data
words = words[:8]

words[0], words[1] = big.Word(z[0]), big.Word(z[0]>>32)
words[2], words[3] = big.Word(z[1]), big.Word(z[1]>>32)
words[4], words[5] = big.Word(z[2]), big.Word(z[2]>>32)
words[6], words[7] = big.Word(z[3]), big.Word(z[3]>>32)

// Feed it back to normalize (up or down within the big.Int)
(*b).SetBits(words)
} else {
// Not enough space to set all the words, have to allocate
words := [8]big.Word{
big.Word(z[0]), big.Word(z[0] >> 32),
big.Word(z[1]), big.Word(z[1] >> 32),
big.Word(z[2]), big.Word(z[2] >> 32),
big.Word(z[3]), big.Word(z[3] >> 32),
}
(*b).SetBits(words[:])
}
b.SetBits(words[:])
}
return b
}

// FromBig is a convenience-constructor from big.Int.
Expand Down
46 changes: 46 additions & 0 deletions conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,30 @@ func TestToBig(t *testing.T) {
}
}

func TestIntoBig(t *testing.T) {
var uint256Nil *Int

bigNil := new(big.Int)
if uint256Nil.IntoBig(&bigNil); bigNil != nil {
t.Errorf("want big.Int <nil>, have %x", bigNil)
}
var bigZero *big.Int
if new(Int).IntoBig(&bigZero); bigZero.Cmp(new(big.Int)) != 0 {
t.Errorf("expected big.Int 0, got %x", bigZero)
}
var b *big.Int
for i := uint(0); i < 256; i++ {
f := new(Int).SetUint64(1)
f.Lsh(f, i)
f.IntoBig(&b)
expected := big.NewInt(1)
expected.Lsh(expected, i)
if b.Cmp(expected) != 0 {
t.Fatalf("expected %x, got %x", expected, b)
}
}
}

func BenchmarkScanScientific(b *testing.B) {
intsub1 := new(Int)
_ = intsub1.fromDecimal(twoPow256Sub1)
Expand Down Expand Up @@ -324,6 +348,28 @@ func BenchmarkToBig(bench *testing.B) {
bench.Run("4words", func(bench *testing.B) { benchToBig(bench, param4) })
}

func benchIntoBig(bench *testing.B, f *Int) *big.Int {
var b *big.Int
for i := 0; i < bench.N; i++ {
f.IntoBig(&b)
}
return b
}

func BenchmarkIntoBig(bench *testing.B) {
param1 := new(Int).SetUint64(0xff)
bench.Run("1word", func(bench *testing.B) { benchIntoBig(bench, param1) })

param2 := new(Int).Lsh(param1, 64)
bench.Run("2words", func(bench *testing.B) { benchIntoBig(bench, param2) })

param3 := new(Int).Lsh(param2, 64)
bench.Run("3words", func(bench *testing.B) { benchIntoBig(bench, param3) })

param4 := new(Int).Lsh(param3, 64)
bench.Run("4words", func(bench *testing.B) { benchIntoBig(bench, param4) })
}

func TestFormat(t *testing.T) {
testCases := []string{
"0",
Expand Down

0 comments on commit 9fb9e97

Please sign in to comment.