Skip to content

Conversation

@gregriff
Copy link
Contributor

@gregriff gregriff commented Sep 21, 2025

I have a bubbletea app that uses Glamour to render Markdown many times per second, just like in mods. While profiling I noticed that RGBColor.Sequence() was taking up 27% of the CPU time during Markdown rendering. Most of this was due to go-colorful.Hex(), but this has recently been optimized in the v1.3.0 release, which I created an issue for here #192 .

But even after updating go-colorful in my termenv fork, RGBColor.Sequence() was still taking up 11% of CPU time. Manually creating the ANSI sequence led to a great speed-up with less allocations. Since the other two Sequence() functions also used the slow fmt.Sprintf I rewrote those as well.

RGBColor.Sequence(): 2.7x faster, half the allocations
ANSI256Color.Sequence(): 5.7x faster, half the allocations
ANSIColor.Sequence(): 12.8x faster, 0 allocations

The benchmarks below show up to three function variants. The originals are labeled fmt.Sprintf. The functions in this PR are labeled []byte or strconv.FormatInt after their mechanisms of performance gain. There are also results labeled strings.Builder, which are included in case that style of code is preferred, even if less performant. All implementations in this benchmark can be found here. The benchmark can be found here. If the strings.Builder style is preferred I'd be happy to make a new PR with those implementations.

Note: this benchmark uses go-colorful v1.3.0

go test -run=^$ -bench=. -benchmem

cpu: Apple M1
BenchmarkSequence/termenv.ANSI256Color:fmt.Sprintf-8         	 9265803	       128.9 ns/op	      24 B/op	       2 allocs/op
BenchmarkSequence/termenv.ANSI256Color:strings.Builder-8     	45521577	        26.65 ns/op	       8 B/op	       1 allocs/op
BenchmarkSequence/termenv.ANSI256Color:[]byte-8              	52240633	        22.51 ns/op	       8 B/op	       1 allocs/op

BenchmarkSequence/termenv.RGBColor:fmt.Sprintf-8             	 5904944	       205.0 ns/op	      32 B/op	       2 allocs/op
BenchmarkSequence/termenv.RGBColor:strings.Builder-8         	 9403798	       128.2 ns/op	      25 B/op	       4 allocs/op
BenchmarkSequence/termenv.RGBColor:[]byte-8                  	16015410	        75.76 ns/op	      16 B/op	       1 allocs/op

BenchmarkSequence/termenv.ANSIColor:fmt.Sprintf-8            	21228495	        56.95 ns/op	       2 B/op	       1 allocs/op
BenchmarkSequence/termenv.ANSIColor:strconv.FormatInt-8      	268179271	         4.463 ns/op	       0 B/op	       0 allocs/op

PASS
ok  	github.com/muesli/termenv	9.833s

Since the strings.Builder and the []byte versions read so similar, I advocate for the []byte implementations in this PR, but I understand if none of these fit the code-style. In either case, I think the ANSIColor.Sequence() rewrite makes the most sense due to it being a small change and large speed-up.

Programs using Glamour would benefit greatly from this PR but surely there are other programs that call Sequence() in a hot loop.

@muesli
Copy link
Owner

muesli commented Sep 24, 2025

Exciting, checking it out.

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