diff --git a/.gitignore b/.gitignore index 44b9eef..1fa5e67 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,8 @@ *.swp .vscode/ -/example/ \ No newline at end of file +/example/ + +# Go profiling files +*.out +*.test diff --git a/fetchall/main.go b/fetchall/main.go index d8603d1..29783d6 100644 --- a/fetchall/main.go +++ b/fetchall/main.go @@ -10,50 +10,50 @@ import ( "time" ) -type HttpGetResult struct { +type HTTPGetResult struct { Success bool Body string Elapsed time.Duration Size int - Url string + URL string Error error } -func (result HttpGetResult) String() string { +func (result HTTPGetResult) String() string { if result.Success { - return fmt.Sprintf("%v\t%v\t%v", result.Elapsed, result.Size, result.Url) + return fmt.Sprintf("%v\t%v\t%v", result.Elapsed, result.Size, result.URL) } else { return result.Error.Error() } } -func GetHttpBody(url string, ch chan HttpGetResult) { +func GetHTTPBody(url string, ch chan HTTPGetResult) { startTime := time.Now() resp, err := http.Get(url) if err != nil { - ch <- HttpGetResult{Success: false, Error: err} + ch <- HTTPGetResult{Success: false, Error: err} return } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { - ch <- HttpGetResult{Success: false, Error: err} + ch <- HTTPGetResult{Success: false, Error: err} return } - ch <- HttpGetResult{ + ch <- HTTPGetResult{ Success: true, Body: string(data), Elapsed: time.Since(startTime), Size: len(data), - Url: url, + URL: url, } } func main() { startTime := time.Now() - urls, ch := os.Args[1:], make(chan HttpGetResult) + urls, ch := os.Args[1:], make(chan HTTPGetResult) for _, url := range urls { - go GetHttpBody(url, ch) + go GetHTTPBody(url, ch) } for i := 0; i < len(urls); i++ { fmt.Println(<-ch) diff --git a/varfmt/fmt.go b/varfmt/fmt.go index b093e45..62789f7 100644 --- a/varfmt/fmt.go +++ b/varfmt/fmt.go @@ -2,6 +2,74 @@ package varfmt -func Sprintf(format string, args ...interface{}) string { - return "" +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" +) + +// fmt.Sprintf does 1 alloc +// this does 3-4 allocations and is a bit slower than fmt.Sprintf +// can be further optimized, by casting args slice +// to string without allocating new space (somehow) +func Sprintf(format string, args ...any) string { + // init output strings.Builder and num rune buffer + // cant use strings.Builder for storing num since + // builder can't clear its contents without reallocating + output, num := strings.Builder{}, make([]rune, 0, 1) + // convert args slice to strArgs string slice + strArgs, maxLen := make([]string, len(args), cap(args)), 0 + for i, arg := range args { + strArgs[i] = fmt.Sprint(arg) + if len(strArgs[i]) > maxLen { + maxLen = len(strArgs[i]) + } + } + // allocate maximum possible length of the resulting string + output.Grow(len(format) + len(args)*maxLen) + start, argIndex := 0, 0 + // decode runes one by one + for start < len(format) { + r, n := utf8.DecodeRuneInString(format[start:]) + start += n + // if specifier is found, start forming index + if r == '{' { + numLength := 0 + for { + r, n = utf8.DecodeRuneInString(format[start:]) + start += n + if r == '}' { + break + } + numLength++ + // append new rune if the number is bigger + // than the current buffer + if numLength >= len(num) { + num = append(num, r) + } else { + num[numLength] = r + } + } + var index int + // if specifier is empty get arg at argIndex + // otherwise get arg at the specified index from num + if len(num) == 0 { + index = argIndex + } else { + var err error + index, err = strconv.Atoi(string(num)) + if err != nil { + panic(err) + } + } + output.WriteString(strArgs[index]) + argIndex++ + // set len of num to 0, but keep capacity + num = num[:0] + continue + } + output.WriteRune(r) + } + return output.String() } diff --git a/varfmt/fmt_test.go b/varfmt/fmt_test.go index c88befe..be8efbc 100644 --- a/varfmt/fmt_test.go +++ b/varfmt/fmt_test.go @@ -9,49 +9,49 @@ import ( ) func TestFormat(t *testing.T) { - s := make([]interface{}, 1002) + s := make([]any, 1002) s[10] = 1 s[100] = 2 s[1000] = 3 for _, tc := range []struct { format string - args []interface{} + args []any result string }{ { format: "{}", - args: []interface{}{0}, + args: []any{0}, result: "0", }, { format: "{0} {0}", - args: []interface{}{1}, + args: []any{1}, result: "1 1", }, { format: "{1} {5}", - args: []interface{}{0, 1, 2, 3, 4, 5, 6}, + args: []any{0, 1, 2, 3, 4, 5, 6}, result: "1 5", }, { format: "{} {} {} {} {}", - args: []interface{}{0, 1, 2, 3, 4}, + args: []any{0, 1, 2, 3, 4}, result: "0 1 2 3 4", }, { format: "{} {0} {0} {0} {}", - args: []interface{}{0, 1, 2, 3, 4}, + args: []any{0, 1, 2, 3, 4}, result: "0 0 0 0 4", }, { format: "Hello, {2}", - args: []interface{}{0, 1, "World"}, + args: []any{0, 1, "World"}, result: "Hello, World", }, { format: "He{2}o", - args: []interface{}{0, 1, "ll"}, + args: []any{0, 1, "ll"}, result: "Hello", }, { @@ -80,22 +80,22 @@ func BenchmarkFormat(b *testing.B) { for _, tc := range []struct { name string format string - args []interface{} + args []any }{ { name: "small int", format: "{}", - args: []interface{}{42}, + args: []any{42}, }, { name: "small string", format: "{} {}", - args: []interface{}{"Hello", "World"}, + args: []any{"Hello", "World"}, }, { name: "big", format: strings.Repeat("{0}{1}", 1000), - args: []interface{}{42, 43}, + args: []any{42, 43}, }, } { b.Run(tc.name, func(b *testing.B) { @@ -111,21 +111,21 @@ func BenchmarkSprintf(b *testing.B) { for _, tc := range []struct { name string format string - args []interface{} + args []any }{ { name: "small", format: "%d", - args: []interface{}{42}, + args: []any{42}, }, { name: "small string", format: "%v %v", - args: []interface{}{"Hello", "World"}, + args: []any{"Hello", "World"}, }, { name: "big", format: strings.Repeat("%[0]v%[1]v", 1000), - args: []interface{}{42, 43}, + args: []any{42, 43}, }, } { b.Run(tc.name, func(b *testing.B) {