perf: encode the Typst source straight into native memory - #5
Closed
msallin wants to merge 1 commit into
Closed
Conversation
Encoding.UTF8.GetBytes copied the whole source onto the managed heap for the duration of one native call and left it as garbage afterwards. The Rust side copies the bytes into an owned String, so the managed array was never more than a staging buffer. A 200 KB source measured 200,024 bytes of managed heap; large sources land on the large object heap, which is only reclaimed by a gen2 collection. Encoding into a native block instead removes that entirely, and also removes the one-byte placeholder array the empty-source case needed to avoid handing across a null pointer. The native allocations now happen inside the try whose finally releases them. They were made before it, so a throw from the caller-supplied font path sequence or from serializing the system inputs leaked everything allocated up to that point.
msallin
force-pushed
the
feat/source-utf8-native-encode
branch
from
September 6, 2026 19:16
d895d84 to
f767fd4
Compare
Member
Author
|
Reopened upstream against the parent repository: evolvedlight#48 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Encoding.UTF8.GetBytes(inputSource)copied the whole source onto the managed heap, pinned it for one native call, and left it as garbage afterwards. The Rust side copies the bytes into an ownedString, so the managed array was never more than a staging buffer.Measured for a 200 KB source: 200,024 bytes on the managed heap, against zero when encoding into native memory. Anything at or above 85,000 bytes goes to the large object heap, which is only reclaimed by a gen2 collection. This is the path the one-shot statics take, since
TypstCompiler.CompilePdf(source)builds a compiler per call.The change also removes the
new byte[1]placeholder. It existed only becausefixedover an empty array yields a null pointer, which the native side reads as "no in-memory source"; an explicit one-byte allocation carries that distinction directly. The(pointer, length)contract is unchanged, so a source containing NUL bytes still survives intact.Also in this change
The native allocations now happen inside the
trywhosefinallyreleases them. They were made before it, and two statements in between can throw:fontPaths.ToList()over a caller-supplied lazy sequence, andJsonSerializer.Serializefor the system inputs. Either would leak every native block allocated up to that point. Adding one more allocation to that window without closing it would have made an existing leak worse.Tests
Two safety-net tests added before the refactor and confirmed green against the old implementation:
EmptySourceIsDistinguishedFromNoSourceAtAllpins the empty-source-versus-null-pointer distinction the placeholder used to carry.LargeSourceWithMultiByteCharactersIsCompiledInFullcovers a source where UTF-8 byte count and char count diverge.58/58 tests pass. Builds clean on net8, net9 and net10 with zero warnings.