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/
|
.vscode/
|
||||||
|
|
||||||
/example/
|
/example/
|
||||||
|
|
||||||
|
# Go profiling files
|
||||||
|
*.out
|
||||||
|
*.test
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue