Skip to content

Fix HXCPP_GC_BIG_BLOCKS row count regression - #1409

Open
dimensionscape wants to merge 1 commit into
HaxeFoundation:masterfrom
dimensionscape:fix/gc-big-blocks-row-counts
Open

dimensionscape wants to merge 1 commit into
HaxeFoundation:masterfrom
dimensionscape:fix/gc-big-blocks-row-counts

Conversation

@dimensionscape

Copy link
Copy Markdown
Contributor

#1402 changed mUsedRows and mFraggedRows in BlockDataInfo from int to uint8_t. That's enough for the default 32k blocks, which have 254 usable lines, but HXCPP_GC_BIG_BLOCKS uses 64k blocks with 508 usable lines, so the counts wrap once a block has more than 255 lines in use.

The worst effect is that the GC badly underestimates how much memory is in use after a collection. The working-memory target it sets stays far too small, so it ends up collecting almost every time it needs a new block, and a big-blocks build with a reasonable amount of live data spends most of its time in the GC. The wrap also means countRows() never sees a full block as full, and a block with exactly 256 used lines looks empty.

This changes both fields back to uint16_t, along with the allocator's mFraggedRows pointer and the casts into it. It also adds static_asserts so the counts can't end up smaller than IMMIX_USEFUL_LINES again. The byte counts are fine as uint16_t (508 x 128 = 65024).

See Repro:

Build with -D HXCPP_GC_BIG_BLOCKS and compare against a build without it. With the define, the churn loop is dramatically slower.

class Main {
	static function main() {
		var t0 = haxe.Timer.stamp();
		var live = new Array<Node>();
		for (i in 0...6000000)
			live.push(new Node(i));
		var t1 = haxe.Timer.stamp();
		var sum = 0;
		for (i in 0...30000000)
			sum += new Node(i).v & 1;
		var t2 = haxe.Timer.stamp();
		Sys.println('live set ${Std.int((t1 - t0) * 1000)}ms, churn ${Std.int((t2 - t1) * 1000)}ms ($sum, ${live.length})');
	}
}

class Node {
	public var v:Int;
	public var next:Node;

	public function new(v:Int)
		this.v = v;
}

Note: on current master you may also hit the marking crash fixed by #1407 while testing this.

HaxeFoundation#1402 made BlockDataInfo's mUsedRows and mFraggedRows uint8_t, which
fits the default 32k blocks (254 usable lines) but not big blocks
(508). With HXCPP_GC_BIG_BLOCKS the counts wrap, the GC underestimates
the live heap, and it ends up collecting on almost every new block.

Use uint16_t for both (and for the allocator's mFraggedRows pointer),
with static_asserts so they can't drop below IMMIX_USEFUL_LINES again.
Comment thread src/hx/gc/Immix.cpp
};

// A block's row counts run up to IMMIX_USEFUL_LINES: 254 with the default 32k blocks, but 508 with
// HXCPP_GC_BIG_BLOCKS' 64k blocks, more than a uint8_t holds.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment doesn't seem very useful to keep, since it refers to the uint8_t that is removed by this PR. I think the static assert is more helpful at documenting the requirement

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's fair. It was originally a warning against regression, but the static asserts keep us safe. I'll remove it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants