Updated .gitignore to ignore .out and .test profiling files

Replaced interface{} with any in varfmt
Implemented varfmt, could be further optimized
This commit is contained in:
Egor 2024-06-03 20:25:40 +03:00
parent 300704d0c1
commit bfb0daa1ef
4 changed files with 103 additions and 31 deletions

4
.gitignore vendored
View file

@ -3,3 +3,7 @@
.vscode/ .vscode/
/example/ /example/
# Go profiling files
*.out
*.test

View file

@ -10,50 +10,50 @@ import (
"time" "time"
) )
type HttpGetResult struct { type HTTPGetResult struct {
Success bool Success bool
Body string Body string
Elapsed time.Duration Elapsed time.Duration
Size int Size int
Url string URL string
Error error Error error
} }
func (result HttpGetResult) String() string { func (result HTTPGetResult) String() string {
if result.Success { 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 { } else {
return result.Error.Error() return result.Error.Error()
} }
} }
func GetHttpBody(url string, ch chan HttpGetResult) { func GetHTTPBody(url string, ch chan HTTPGetResult) {
startTime := time.Now() startTime := time.Now()
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
ch <- HttpGetResult{Success: false, Error: err} ch <- HTTPGetResult{Success: false, Error: err}
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
data, err := io.ReadAll(resp.Body) data, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
ch <- HttpGetResult{Success: false, Error: err} ch <- HTTPGetResult{Success: false, Error: err}
return return
} }
ch <- HttpGetResult{ ch <- HTTPGetResult{
Success: true, Success: true,
Body: string(data), Body: string(data),
Elapsed: time.Since(startTime), Elapsed: time.Since(startTime),
Size: len(data), Size: len(data),
Url: url, URL: url,
} }
} }
func main() { func main() {
startTime := time.Now() startTime := time.Now()
urls, ch := os.Args[1:], make(chan HttpGetResult) urls, ch := os.Args[1:], make(chan HTTPGetResult)
for _, url := range urls { for _, url := range urls {
go GetHttpBody(url, ch) go GetHTTPBody(url, ch)
} }
for i := 0; i < len(urls); i++ { for i := 0; i < len(urls); i++ {
fmt.Println(<-ch) fmt.Println(<-ch)

View file

@ -2,6 +2,74 @@
package varfmt package varfmt
func Sprintf(format string, args ...interface{}) string { import (
return "" "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()
} }

View file

@ -9,49 +9,49 @@ import (
) )
func TestFormat(t *testing.T) { func TestFormat(t *testing.T) {
s := make([]interface{}, 1002) s := make([]any, 1002)
s[10] = 1 s[10] = 1
s[100] = 2 s[100] = 2
s[1000] = 3 s[1000] = 3
for _, tc := range []struct { for _, tc := range []struct {
format string format string
args []interface{} args []any
result string result string
}{ }{
{ {
format: "{}", format: "{}",
args: []interface{}{0}, args: []any{0},
result: "0", result: "0",
}, },
{ {
format: "{0} {0}", format: "{0} {0}",
args: []interface{}{1}, args: []any{1},
result: "1 1", result: "1 1",
}, },
{ {
format: "{1} {5}", format: "{1} {5}",
args: []interface{}{0, 1, 2, 3, 4, 5, 6}, args: []any{0, 1, 2, 3, 4, 5, 6},
result: "1 5", result: "1 5",
}, },
{ {
format: "{} {} {} {} {}", format: "{} {} {} {} {}",
args: []interface{}{0, 1, 2, 3, 4}, args: []any{0, 1, 2, 3, 4},
result: "0 1 2 3 4", result: "0 1 2 3 4",
}, },
{ {
format: "{} {0} {0} {0} {}", format: "{} {0} {0} {0} {}",
args: []interface{}{0, 1, 2, 3, 4}, args: []any{0, 1, 2, 3, 4},
result: "0 0 0 0 4", result: "0 0 0 0 4",
}, },
{ {
format: "Hello, {2}", format: "Hello, {2}",
args: []interface{}{0, 1, "World"}, args: []any{0, 1, "World"},
result: "Hello, World", result: "Hello, World",
}, },
{ {
format: "He{2}o", format: "He{2}o",
args: []interface{}{0, 1, "ll"}, args: []any{0, 1, "ll"},
result: "Hello", result: "Hello",
}, },
{ {
@ -80,22 +80,22 @@ func BenchmarkFormat(b *testing.B) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
format string format string
args []interface{} args []any
}{ }{
{ {
name: "small int", name: "small int",
format: "{}", format: "{}",
args: []interface{}{42}, args: []any{42},
}, },
{ {
name: "small string", name: "small string",
format: "{} {}", format: "{} {}",
args: []interface{}{"Hello", "World"}, args: []any{"Hello", "World"},
}, },
{ {
name: "big", name: "big",
format: strings.Repeat("{0}{1}", 1000), format: strings.Repeat("{0}{1}", 1000),
args: []interface{}{42, 43}, args: []any{42, 43},
}, },
} { } {
b.Run(tc.name, func(b *testing.B) { b.Run(tc.name, func(b *testing.B) {
@ -111,21 +111,21 @@ func BenchmarkSprintf(b *testing.B) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
format string format string
args []interface{} args []any
}{ }{
{ {
name: "small", name: "small",
format: "%d", format: "%d",
args: []interface{}{42}, args: []any{42},
}, },
{ {
name: "small string", name: "small string",
format: "%v %v", format: "%v %v",
args: []interface{}{"Hello", "World"}, args: []any{"Hello", "World"},
}, { }, {
name: "big", name: "big",
format: strings.Repeat("%[0]v%[1]v", 1000), format: strings.Repeat("%[0]v%[1]v", 1000),
args: []interface{}{42, 43}, args: []any{42, 43},
}, },
} { } {
b.Run(tc.name, func(b *testing.B) { b.Run(tc.name, func(b *testing.B) {