From 06ee648f7a085a22737b284f4f0af8e8d7dd95b4 Mon Sep 17 00:00:00 2001 From: Jason Walton Date: Tue, 12 Oct 2021 10:00:10 -0400 Subject: [PATCH] perf(ansistyles): Use LUT for byte to string conversions. This about doubles the speed of generating 16.7m color ANSI escape codes, because we don't have to go through strconv. --- pkg/ansistyles/ansistyles.go | 41 +++-- pkg/ansistyles/byteToString.go | 264 +++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+), 21 deletions(-) create mode 100644 pkg/ansistyles/byteToString.go diff --git a/pkg/ansistyles/ansistyles.go b/pkg/ansistyles/ansistyles.go index 68d1e47..b53c3cb 100644 --- a/pkg/ansistyles/ansistyles.go +++ b/pkg/ansistyles/ansistyles.go @@ -102,7 +102,6 @@ import ( "fmt" "math" "regexp" - "strconv" "strings" ) @@ -124,13 +123,13 @@ 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") } @@ -138,13 +137,13 @@ func WriteStringAnsi(out *strings.Builder, color uint8) { // // `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") } @@ -152,13 +151,13 @@ func WriteStringBgAnsi(out *strings.Builder, color uint8) { // // 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") } @@ -166,51 +165,51 @@ func WriteStringAnsi256(out *strings.Builder, color uint8) { // // 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") } diff --git a/pkg/ansistyles/byteToString.go b/pkg/ansistyles/byteToString.go new file mode 100644 index 0000000..08e780b --- /dev/null +++ b/pkg/ansistyles/byteToString.go @@ -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", +}