From aec0177543434f275e1b5f87aab383c04f799f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 22 Jul 2024 15:56:15 +0300 Subject: [PATCH 1/2] conversion: introduce 0-alloc IntoBig method --- benchmarks_test.go | 1 - conversion.go | 61 ++++++++++++++++++++++++++++++++++++++-------- conversion_test.go | 22 +++++++++++++++++ 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/benchmarks_test.go b/benchmarks_test.go index 210dfaa..6a18c9d 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -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")) diff --git a/conversion.go b/conversion.go index b1742fb..50c5276 100644 --- a/conversion.go +++ b/conversion.go @@ -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. diff --git a/conversion_test.go b/conversion_test.go index 5e7b66a..5a4493c 100644 --- a/conversion_test.go +++ b/conversion_test.go @@ -324,6 +324,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", From 2d03468f3de25ad173708c0ae4ee1844693d9cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 22 Jul 2024 16:15:17 +0300 Subject: [PATCH 2/2] conversion: add tests for different pathways --- conversion_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/conversion_test.go b/conversion_test.go index 5a4493c..b14d2a1 100644 --- a/conversion_test.go +++ b/conversion_test.go @@ -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 , 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)