From 94de2560e93907a348c1dd4e75ac83d6910cb816 Mon Sep 17 00:00:00 2001 From: Filip Vranesevic Date: Fri, 8 Sep 2023 20:56:28 +0200 Subject: [PATCH 1/6] Scan optimization --- src/runtime/gc_blocks.go | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 3c3862dbba..b7baaedc7b 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -32,6 +32,7 @@ package runtime import ( "internal/task" + "math/bits" "runtime/interrupt" "unsafe" ) @@ -76,6 +77,11 @@ const ( blockStateMask blockState = 3 // 11 ) +// blockStateByteAllTails is a byte containing 4 times blockStateTail bits. +// It assumes there are 2 state bits per block, otherwise it might have to be +// turned into variable and assigned using inline function. +const blockStateByteAllTails = uint8(blockStateTail<<(stateBits*3) | blockStateTail<<(stateBits*2) | blockStateTail<<(stateBits*1) | blockStateTail<<(stateBits*0)) + // String returns a human-readable version of the block state, for debugging. func (s blockState) String() string { switch s { @@ -123,9 +129,32 @@ func (b gcBlock) address() uintptr { // points to an allocated object. It returns the same block if this block // already points to the head. func (b gcBlock) findHead() gcBlock { - for b.state() == blockStateTail { - b-- + stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) + + // XOR the stateByte with byte containing all tails to turn tail bits to 0 + // and shift out the bits that are not part of the object + stateByte := ((*stateBytePtr) ^ blockStateByteAllTails) << ((blocksPerStateByte - (b%blocksPerStateByte + 1)) * stateBits) + // if stateByte is 0 that means all blocks are tails so we loop trough subsequent states, + // byte at a time to find the first byte that is not all tails + if stateByte == 0 { + // subtract the number of object blocks that were in the first byte + b -= (b%blocksPerStateByte + 1) + // skip to next byte + stateBytePtr = (*uint8)(unsafe.Add(unsafe.Pointer(stateBytePtr), -1)) + // loop until state byte is not all tails + for (*stateBytePtr)^blockStateByteAllTails == 0 { + stateBytePtr = (*uint8)(unsafe.Add(unsafe.Pointer(stateBytePtr), -1)) + b -= blocksPerStateByte + } + // set stateByte variable to the first byte that is not all tails and turn all tail bits to zeroes + stateByte = (*stateBytePtr) ^ blockStateByteAllTails } + + // at this point stateByte is set to the first state byte of the object that we encountered which is not all tails + // and all tail bits in it are turned to zero. We count number of bytes that are 0 (tail) using LeadingZeros8 + // and divide it by stateBits to get the number of tail blocks in state bits. + b -= gcBlock(bits.LeadingZeros8(stateByte) / stateBits) + if gcAsserts { if b.state() != blockStateHead && b.state() != blockStateMark { runtimePanic("gc: found tail without head") From 6dada43d8c391b957001bad61f5302ba92640f71 Mon Sep 17 00:00:00 2001 From: Filip Vranesevic Date: Sat, 9 Sep 2023 16:07:00 +0200 Subject: [PATCH 2/6] changed expected size so all tests can run --- builder/sizes_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 8b669462a5..78d6cfe38c 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -41,9 +41,16 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers +<<<<<<< HEAD {"hifive1b", "examples/echo", 4568, 280, 0, 2268}, {"microbit", "examples/serial", 2868, 388, 8, 2272}, {"wioterminal", "examples/pininterrupt", 6104, 1484, 116, 6832}, +======= + + {"hifive1b", "examples/echo", 4720, 280, 0, 2252}, + {"microbit", "examples/serial", 2820, 388, 8, 2256}, + {"wioterminal", "examples/pininterrupt", 6091, 1485, 116, 6816}, +>>>>>>> 3214fcb5 (changed expected size so all tests can run) // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the From c0e19b83902bb1385b83650af67d8eff9223da39 Mon Sep 17 00:00:00 2001 From: Filip Vranesevic Date: Sat, 9 Sep 2023 22:43:42 +0200 Subject: [PATCH 3/6] Simplify algorithm --- builder/sizes_test.go | 6 ------ src/runtime/gc_blocks.go | 34 ++++++++++++---------------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 78d6cfe38c..27d3a3d5ec 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -41,16 +41,10 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers -<<<<<<< HEAD - {"hifive1b", "examples/echo", 4568, 280, 0, 2268}, - {"microbit", "examples/serial", 2868, 388, 8, 2272}, - {"wioterminal", "examples/pininterrupt", 6104, 1484, 116, 6832}, -======= {"hifive1b", "examples/echo", 4720, 280, 0, 2252}, {"microbit", "examples/serial", 2820, 388, 8, 2256}, {"wioterminal", "examples/pininterrupt", 6091, 1485, 116, 6816}, ->>>>>>> 3214fcb5 (changed expected size so all tests can run) // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index b7baaedc7b..41a380d038 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -125,35 +125,25 @@ func (b gcBlock) address() uintptr { return addr } -// findHead returns the head (first block) of an object, assuming the block -// points to an allocated object. It returns the same block if this block -// already points to the head. func (b gcBlock) findHead() gcBlock { stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) - // XOR the stateByte with byte containing all tails to turn tail bits to 0 - // and shift out the bits that are not part of the object - stateByte := ((*stateBytePtr) ^ blockStateByteAllTails) << ((blocksPerStateByte - (b%blocksPerStateByte + 1)) * stateBits) - // if stateByte is 0 that means all blocks are tails so we loop trough subsequent states, - // byte at a time to find the first byte that is not all tails - if stateByte == 0 { - // subtract the number of object blocks that were in the first byte - b -= (b%blocksPerStateByte + 1) - // skip to next byte + // XOR the stateByte with byte containing all tails to turn tail bits to 0 and + // mask out the bits that are not part of the object + otherObjectBlocks := int(blocksPerStateByte - (b%blocksPerStateByte + 1)) + stateByte := ((*stateBytePtr) ^ blockStateByteAllTails) & (uint8(1<<(8-(otherObjectBlocks*stateBits))) - 1) + + // loop until state byte is not all tails + for stateByte == 0 { stateBytePtr = (*uint8)(unsafe.Add(unsafe.Pointer(stateBytePtr), -1)) - // loop until state byte is not all tails - for (*stateBytePtr)^blockStateByteAllTails == 0 { - stateBytePtr = (*uint8)(unsafe.Add(unsafe.Pointer(stateBytePtr), -1)) - b -= blocksPerStateByte - } - // set stateByte variable to the first byte that is not all tails and turn all tail bits to zeroes stateByte = (*stateBytePtr) ^ blockStateByteAllTails + b -= blocksPerStateByte } - // at this point stateByte is set to the first state byte of the object that we encountered which is not all tails - // and all tail bits in it are turned to zero. We count number of bytes that are 0 (tail) using LeadingZeros8 - // and divide it by stateBits to get the number of tail blocks in state bits. - b -= gcBlock(bits.LeadingZeros8(stateByte) / stateBits) + // in the first state byte which is not all tails, count the number of leading bits that are 0 and + // divide it by stateBits to get the number of tail blocks. Subtract otherObjectBlocks to exclude + // blocks that are not part of the object + b -= gcBlock((bits.LeadingZeros8(stateByte) / stateBits) - otherObjectBlocks) if gcAsserts { if b.state() != blockStateHead && b.state() != blockStateMark { From 1fdd35d986ed6256ff0a56c24311da5f0991059f Mon Sep 17 00:00:00 2001 From: Filip Vranesevic Date: Sat, 9 Sep 2023 22:48:46 +0200 Subject: [PATCH 4/6] restoring original comment --- src/runtime/gc_blocks.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 41a380d038..0dced673f8 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -125,6 +125,9 @@ func (b gcBlock) address() uintptr { return addr } +// findHead returns the head (first block) of an object, assuming the block +// points to an allocated object. It returns the same block if this block +// already points to the head. func (b gcBlock) findHead() gcBlock { stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) From ddc79dfc42a77a3a726aa7f1547e9b7832e4c1c7 Mon Sep 17 00:00:00 2001 From: Filip Vranesevic Date: Fri, 20 Sep 2024 12:23:13 +0200 Subject: [PATCH 5/6] updated new binary sizes for TestBinarySize --- builder/sizes_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 27d3a3d5ec..b207d92e1e 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -41,10 +41,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - - {"hifive1b", "examples/echo", 4720, 280, 0, 2252}, - {"microbit", "examples/serial", 2820, 388, 8, 2256}, - {"wioterminal", "examples/pininterrupt", 6091, 1485, 116, 6816}, + {"hifive1b", "examples/echo", 4688, 280, 0, 2268}, + {"microbit", "examples/serial", 2952, 388, 8, 2272}, + {"wioterminal", "examples/pininterrupt", 6148, 1484, 116, 6832}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the From 47f5d3c1d2b54ed2fea7a06e5040d0c484a587fc Mon Sep 17 00:00:00 2001 From: HattoriHanzo031 Date: Thu, 31 Oct 2024 01:10:46 +0100 Subject: [PATCH 6/6] test binary size update --- builder/sizes_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index b207d92e1e..7a99d784e8 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -41,7 +41,7 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 4688, 280, 0, 2268}, + {"hifive1b", "examples/echo", 4692, 280, 0, 2268}, {"microbit", "examples/serial", 2952, 388, 8, 2272}, {"wioterminal", "examples/pininterrupt", 6148, 1484, 116, 6832},