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:
parent
300704d0c1
commit
bfb0daa1ef
4 changed files with 103 additions and 31 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,3 +3,7 @@
|
|||
.vscode/
|
||||
|
||||
/example/
|
||||
|
||||
# Go profiling files
|
||||
*.out
|
||||
*.test
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue