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

6
.gitignore vendored
View file

@ -2,4 +2,8 @@
*.swp
.vscode/
/example/
/example/
# Go profiling files
*.out
*.test

View file

@ -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)

View file

@ -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()
}

View file

@ -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) {