From e4a8bbaa846511672ac559cb0994f259869ec4dc Mon Sep 17 00:00:00 2001 From: Will Beason Date: Wed, 8 Nov 2023 15:39:44 -0600 Subject: [PATCH] Reduce runtime of Encode() by 20% This is a performance optimization change for Encode(). As-is, Encode() always calls a stringconcat (by "+"-ing strings together). This call is unnecessary as we know the structure of the location codes ahead of time, and non-performant as stringconcat is slow compared to slicebytetostring. To do this, I made the initial byte slice allocated represent the entire final code and immediately fill in the separator. This means later, when we format the code, we don't need to concatenate the Separator in as it is already there. I introduce a special case for the location pair after the separator rather than having those bytes computed in the loop: otherwise we need to have an if-statement within the loop. As a bonus, having these computed first let us to remove the two unnecessary divides that formerly happened at the end of the final loop. (I am unsure if this last point is noticeable - I could not detect a significant difference.) --- go/encode.go | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/go/encode.go b/go/encode.go index 04dcc166..93135b19 100644 --- a/go/encode.go +++ b/go/encode.go @@ -62,7 +62,9 @@ func Encode(lat, lng float64, codeLen int) string { } // Use a char array so we can build it up from the end digits, without having // to keep reallocating strings. - var code [15]byte + var code [16]byte + // Avoid the need for string concatenation by filling in the Separator manually. + code[sepPos] = Separator // Compute the code. // This approach converts each value to an integer after multiplying it by @@ -74,8 +76,8 @@ func Encode(lat, lng float64, codeLen int) string { var latVal int64 = int64(math.Round((lat+latMax)*finalLatPrecision*1e6) / 1e6) var lngVal int64 = int64(math.Round((lng+lngMax)*finalLngPrecision*1e6) / 1e6) - pos := maxCodeLen - 1 // Compute the grid part of the code if necessary. + pos := maxCodeLen if codeLen > pairCodeLen { for i := 0; i < gridCodeLen; i++ { latDigit := latVal % int64(gridRows) @@ -90,22 +92,31 @@ func Encode(lat, lng float64, codeLen int) string { latVal /= gridLatFullValue lngVal /= gridLngFullValue } - pos = pairCodeLen - 1 + + // Compute the pair after the Separator as a special case rather than + // introduce an if statement to the loop which will only be executed once. + // This also allows us to remove two unnecessary divides at the end of the loop. + latNdx := latVal % int64(encBase) + lngNdx := lngVal % int64(encBase) + code[sepPos+2] = Alphabet[lngNdx] + code[sepPos+1] = Alphabet[latNdx] + // Compute the pair section of the code. - for i := 0; i < pairCodeLen/2; i++ { - latNdx := latVal % int64(encBase) - lngNdx := lngVal % int64(encBase) + pos = sepPos - 1 + for i := 0; i < sepPos/2; i++ { + latVal /= int64(encBase) + lngVal /= int64(encBase) + latNdx = latVal % int64(encBase) + lngNdx = lngVal % int64(encBase) code[pos] = Alphabet[lngNdx] pos -= 1 code[pos] = Alphabet[latNdx] pos -= 1 - latVal /= int64(encBase) - lngVal /= int64(encBase) } // If we don't need to pad the code, return the requested section. if codeLen >= sepPos { - return string(code[:sepPos]) + string(Separator) + string(code[sepPos:codeLen]) + return string(code[:codeLen+1]) } // Pad and return the code. return string(code[:codeLen]) + strings.Repeat(string(Padding), sepPos-codeLen) + string(Separator)