Skip to content

Commit

Permalink
perf(ansistyles): Use LUT for byte to string conversions.
Browse files Browse the repository at this point in the history
This about doubles the speed of generating 16.7m color
ANSI escape codes, because we don't have to go through
strconv.
  • Loading branch information
jwalton committed Oct 12, 2021
1 parent de78c08 commit 06ee648
Show file tree
Hide file tree
Showing 2 changed files with 284 additions and 21 deletions.
41 changes: 20 additions & 21 deletions pkg/ansistyles/ansistyles.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
)

Expand All @@ -124,93 +123,93 @@ func namedCSPair(open uint8, close uint8) CSPair {
//
// `color` should be a number between 30 and 37 or 90 and 97, inclusive.
func Ansi(color uint8) string {
return "\u001B[" + strconv.FormatUint(uint64(color), 10) + "m"
return "\u001B[" + byteToString[color] + "m"
}

// WriteStringAnsi writes an ANSI escape code to the given strings.Builder.
func WriteStringAnsi(out *strings.Builder, color uint8) {
out.WriteString("\u001B[")
out.WriteString(strconv.FormatUint(uint64(color), 10))
out.WriteString(byteToString[color])
out.WriteString("m")
}

// BgAnsi returns the string used to set the background color, based on a basic 16-color Ansi code.
//
// `color` should be a number between 30 and 37 or 90 and 97, inclusive.
func BgAnsi(color uint8) string {
return "\u001B[" + strconv.FormatUint(uint64(color+10), 10) + "m"
return "\u001B[" + byteToString[color+10] + "m"
}

// WriteStringBgAnsi writes an ANSI escape code to set the background color to the given strings.Builder.
func WriteStringBgAnsi(out *strings.Builder, color uint8) {
out.WriteString("\u001B[")
out.WriteString(strconv.FormatUint(uint64(color+10), 10))
out.WriteString(byteToString[color+10])
out.WriteString("m")
}

// Ansi256 returns the string used to set the foreground color, based on Ansi 256 color lookup table.
//
// See https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit.
func Ansi256(color uint8) string {
return "\u001B[38;5;" + strconv.FormatUint(uint64(color), 10) + "m"
return "\u001B[38;5;" + byteToString[color] + "m"
}

// WriteStringAnsi256 writes the string used to set the foreground color, based on Ansi 256 color lookup table.
func WriteStringAnsi256(out *strings.Builder, color uint8) {
out.WriteString("\u001B[38;5;")
out.WriteString(strconv.FormatUint(uint64(color), 10))
out.WriteString(byteToString[color])
out.WriteString("m")
}

// BgAnsi256 returns the string used to set the background color, based on Ansi 256 color lookup table.
//
// See https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit.
func BgAnsi256(color uint8) string {
return "\u001B[48;5;" + strconv.FormatUint(uint64(color), 10) + "m"
return "\u001B[48;5;" + byteToString[color] + "m"
}

// WriteStringBgAnsi256 writes the string used to set the background color, based on Ansi 256 color lookup table.
func WriteStringBgAnsi256(out *strings.Builder, color uint8) {
out.WriteString("\u001B[48;5;")
out.WriteString(strconv.FormatUint(uint64(color), 10))
out.WriteString(byteToString[color])
out.WriteString("m")
}

// Ansi16m returns the string used to set a 24bit foreground color.
func Ansi16m(red uint8, green uint8, blue uint8) string {
return "\u001B[38;2;" +
strconv.FormatUint(uint64(red), 10) + ";" +
strconv.FormatUint(uint64(green), 10) + ";" +
strconv.FormatUint(uint64(blue), 10) + "m"
byteToString[red] + ";" +
byteToString[green] + ";" +
byteToString[blue] + "m"
}

// WriteStringAnsi16m writes the string used to set a 24bit foreground color.
func WriteStringAnsi16m(out *strings.Builder, red uint8, green uint8, blue uint8) {
out.WriteString("\u001B[38;2;")
out.WriteString(strconv.FormatUint(uint64(red), 10))
out.WriteString(byteToString[red])
out.WriteString(";")
out.WriteString(strconv.FormatUint(uint64(green), 10))
out.WriteString(byteToString[green])
out.WriteString(";")
out.WriteString(strconv.FormatUint(uint64(blue), 10))
out.WriteString(byteToString[blue])
out.WriteString("m")
}

// BgAnsi16m returns the string used to set a 24bit background color.
func BgAnsi16m(red uint8, green uint8, blue uint8) string {
return "\u001B[48;2;" +
strconv.FormatUint(uint64(red), 10) + ";" +
strconv.FormatUint(uint64(green), 10) + ";" +
strconv.FormatUint(uint64(blue), 10) + "m"
byteToString[red] + ";" +
byteToString[green] + ";" +
byteToString[blue] + "m"
}

// WriteStringBgAnsi16m writes the string used to set a 24bit background color.
func WriteStringBgAnsi16m(out *strings.Builder, red uint8, green uint8, blue uint8) {
out.WriteString("\u001B[48;2;")
out.WriteString(strconv.FormatUint(uint64(red), 10))
out.WriteString(byteToString[red])
out.WriteString(";")
out.WriteString(strconv.FormatUint(uint64(green), 10))
out.WriteString(byteToString[green])
out.WriteString(";")
out.WriteString(strconv.FormatUint(uint64(blue), 10))
out.WriteString(byteToString[blue])
out.WriteString("m")
}

Expand Down
264 changes: 264 additions & 0 deletions pkg/ansistyles/byteToString.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
package ansistyles

// byteToString is a lookup table for converting a byte to a string. This uses
// some extra RAM, but reduces the number of allocs required when writing
// colored strings. This does very little for basic colors, but very much
// improves the performance of 16M color generation.
var byteToString = []string{
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"30",
"31",
"32",
"33",
"34",
"35",
"36",
"37",
"38",
"39",
"40",
"41",
"42",
"43",
"44",
"45",
"46",
"47",
"48",
"49",
"50",
"51",
"52",
"53",
"54",
"55",
"56",
"57",
"58",
"59",
"60",
"61",
"62",
"63",
"64",
"65",
"66",
"67",
"68",
"69",
"70",
"71",
"72",
"73",
"74",
"75",
"76",
"77",
"78",
"79",
"80",
"81",
"82",
"83",
"84",
"85",
"86",
"87",
"88",
"89",
"90",
"91",
"92",
"93",
"94",
"95",
"96",
"97",
"98",
"99",
"100",
"101",
"102",
"103",
"104",
"105",
"106",
"107",
"108",
"109",
"110",
"111",
"112",
"113",
"114",
"115",
"116",
"117",
"118",
"119",
"120",
"121",
"122",
"123",
"124",
"125",
"126",
"127",
"128",
"129",
"130",
"131",
"132",
"133",
"134",
"135",
"136",
"137",
"138",
"139",
"140",
"141",
"142",
"143",
"144",
"145",
"146",
"147",
"148",
"149",
"150",
"151",
"152",
"153",
"154",
"155",
"156",
"157",
"158",
"159",
"160",
"161",
"162",
"163",
"164",
"165",
"166",
"167",
"168",
"169",
"170",
"171",
"172",
"173",
"174",
"175",
"176",
"177",
"178",
"179",
"180",
"181",
"182",
"183",
"184",
"185",
"186",
"187",
"188",
"189",
"190",
"191",
"192",
"193",
"194",
"195",
"196",
"197",
"198",
"199",
"200",
"201",
"202",
"203",
"204",
"205",
"206",
"207",
"208",
"209",
"210",
"211",
"212",
"213",
"214",
"215",
"216",
"217",
"218",
"219",
"220",
"221",
"222",
"223",
"224",
"225",
"226",
"227",
"228",
"229",
"230",
"231",
"232",
"233",
"234",
"235",
"236",
"237",
"238",
"239",
"240",
"241",
"242",
"243",
"244",
"245",
"246",
"247",
"248",
"249",
"250",
"251",
"252",
"253",
"254",
"255",
}

0 comments on commit 06ee648

Please sign in to comment.