Skip to content

Add hw3_bench solution #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions hw3_bench/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package main

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"regexp"
"strings"
// "log"
)

const filePath string = "./data/users.txt"

func SlowSearch(out io.Writer) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported function SlowSearch should have comment or be unexported

file, err := os.Open(filePath)
if err != nil {
panic(err)
}

fileContents, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}

r := regexp.MustCompile("@")
seenBrowsers := []string{}
uniqueBrowsers := 0
foundUsers := ""

lines := strings.Split(string(fileContents), "\n")

users := make([]map[string]interface{}, 0)
for _, line := range lines {
user := make(map[string]interface{})
// fmt.Printf("%v %v\n", err, line)
err := json.Unmarshal([]byte(line), &user)
if err != nil {
panic(err)
}
users = append(users, user)
}

for i, user := range users {

isAndroid := false
isMSIE := false

browsers, ok := user["browsers"].([]interface{})
if !ok {
// log.Println("cant cast browsers")
continue
}

for _, browserRaw := range browsers {
browser, ok := browserRaw.(string)
if !ok {
// log.Println("cant cast browser to string")
continue
}
if ok, err := regexp.MatchString("Android", browser); ok && err == nil {
isAndroid = true
notSeenBefore := true
for _, item := range seenBrowsers {
if item == browser {
notSeenBefore = false
}
}
if notSeenBefore {
// log.Printf("SLOW New browser: %s, first seen: %s", browser, user["name"])
seenBrowsers = append(seenBrowsers, browser)
uniqueBrowsers++
}
}
}

for _, browserRaw := range browsers {
browser, ok := browserRaw.(string)
if !ok {
// log.Println("cant cast browser to string")
continue
}
if ok, err := regexp.MatchString("MSIE", browser); ok && err == nil {
isMSIE = true
notSeenBefore := true
for _, item := range seenBrowsers {
if item == browser {
notSeenBefore = false
}
}
if notSeenBefore {
// log.Printf("SLOW New browser: %s, first seen: %s", browser, user["name"])
seenBrowsers = append(seenBrowsers, browser)
uniqueBrowsers++
}
}
}

if !(isAndroid && isMSIE) {
continue
}

// log.Println("Android and MSIE user:", user["name"], user["email"])
email := r.ReplaceAllString(user["email"].(string), " [at] ")
foundUsers += fmt.Sprintf("[%d] %s <%s>\n", i, user["name"], email)
}

fmt.Fprintln(out, "found users:\n"+foundUsers)
fmt.Fprintln(out, "Total unique browsers", len(seenBrowsers))
}
Binary file added hw3_bench/cpu.out
Binary file not shown.
1,000 changes: 1,000 additions & 0 deletions hw3_bench/data/users.txt

Large diffs are not rendered by default.

233 changes: 233 additions & 0 deletions hw3_bench/fast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package main

import (
json "encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"sync"

easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)

var userPool = sync.Pool{
New: func() interface{} {
return User{}
},
}

type User struct {
Email string `json:"email"`
Browsers []string `json:"browsers"`
Name string `json:"name"`
}

func FastSearch(out io.Writer) {
file, err := os.Open(filePath)
if err != nil {
panic(err)
}

fileContents, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}

seenBrowsers := []string{}
uniqueBrowsers := 0
foundUsers := ""

lines := strings.Split(string(fileContents), "\n")

for i, line := range lines {
user := User{}
//err := json.Unmarshal([]byte(line), &user)
err := user.UnmarshalJSON([]byte(line))
if err != nil {
panic(err)
}
isAndroid := false
isMSIE := false

for _, browser := range user.Browsers {
contansAndroid := strings.Contains(browser, "Android")
contansMSIE := strings.Contains(browser, "MSIE")

if contansAndroid || contansMSIE {
if contansAndroid {
isAndroid = true
}

if contansMSIE {
isMSIE = true
}

notSeenBefore := true
for _, item := range seenBrowsers {
if item == browser {
notSeenBefore = false
break
}
}
if notSeenBefore {
// log.Printf("SLOW New browser: %s, first seen: %s", browser, user["name"])
seenBrowsers = append(seenBrowsers, browser)
uniqueBrowsers++
}
}
}

if !(isAndroid && isMSIE) {
continue
}

// log.Println("Android and MSIE user:", user["name"], user["email"])
email := strings.Replace(user.Email, "@", " [at] ", -1)
foundUsers += fmt.Sprintf("[%d] %s <%s>\n", i, user.Name, email)
}

fmt.Fprintln(out, "found users:\n"+foundUsers)
fmt.Fprintln(out, "Total unique browsers", len(seenBrowsers))
}

// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.

// suppress unused package warning
var (
_ *json.RawMessage
_ *jlexer.Lexer
_ *jwriter.Writer
_ easyjson.Marshaler
)

func easyjson3486653aDecodeHw3Bench(in *jlexer.Lexer, out *User) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "email":
out.Email = string(in.String())
case "browsers":
if in.IsNull() {
in.Skip()
out.Browsers = nil
} else {
in.Delim('[')
if out.Browsers == nil {
if !in.IsDelim(']') {
out.Browsers = make([]string, 0, 4)
} else {
out.Browsers = []string{}
}
} else {
out.Browsers = (out.Browsers)[:0]
}
for !in.IsDelim(']') {
var v1 string
v1 = string(in.String())
out.Browsers = append(out.Browsers, v1)
in.WantComma()
}
in.Delim(']')
}
case "name":
out.Name = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson3486653aEncodeHw3Bench(out *jwriter.Writer, in User) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"email\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Email))
}
{
const prefix string = ",\"browsers\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
if in.Browsers == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v2, v3 := range in.Browsers {
if v2 > 0 {
out.RawByte(',')
}
out.String(string(v3))
}
out.RawByte(']')
}
}
{
const prefix string = ",\"name\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Name))
}
out.RawByte('}')
}

// MarshalJSON supports json.Marshaler interface
func (v User) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson3486653aEncodeHw3Bench(&w, v)
return w.Buffer.BuildBytes(), w.Error
}

// MarshalEasyJSON supports easyjson.Marshaler interface
func (v User) MarshalEasyJSON(w *jwriter.Writer) {
easyjson3486653aEncodeHw3Bench(w, v)
}

// UnmarshalJSON supports json.Unmarshaler interface
func (v *User) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson3486653aDecodeHw3Bench(&r, v)
return r.Error()
}

// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *User) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson3486653aDecodeHw3Bench(l, v)
}
51 changes: 51 additions & 0 deletions hw3_bench/hw3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
Есть функиця, которая что-то там ищет по файлу. Но делает она это не очень быстро. Надо её оптимизировать.

Задание на работу с профайлером pprof.

Цель задания - научиться работать с pprof, находить горячие места в коде, уметь строить профиль потребления cpu и памяти, оптимизировать код с учетом этой информации. Написание самого быстрого решения не является целью задания.

Для генерации графа вам понадобится graphviz. Для пользователей windows не забудьте добавить его в PATH чтобы была доступна команда dot.

Рекомендую внимательно прочитать доп. материалы на русском - там ещё много примеров оптимизации и объяснений как работать с профайлером. Фактически там есть вся информация для выполнения этого задания.

Есть с десяток мест где можно оптимизировать.

Для выполнения задания необходимо чтобы один из параметров ( ns/op, B/op, allocs/op ) был быстрее чем в *BenchmarkSolution* ( fast < solution ) и ещё один лучше *BenchmarkSolution* + 20% ( fast < solution * 1.2), например ( fast allocs/op < 10422*1.2=12506 ).

По памяти ( B/op ) и количеству аллокаций ( allocs/op ) можно ориентироваться ровно на результаты *BenchmarkSolution* ниже, по времени ( ns/op ) - нет, зависит от системы.

Для этого задания увеличено количество проверок с 3-х до 5 за 8 часов.

Результат в fast.go в функцию FastSearch (изначально там то же самое что в SlowSearch).

Пример результатов с которыми будет сравниваться:
```
$ go test -bench . -benchmem

goos: windows

goarch: amd64

BenchmarkSlow-8 10 142703250 ns/op 336887900 B/op 284175 allocs/op

BenchmarkSolution-8 500 2782432 ns/op 559910 B/op 10422 allocs/op

PASS

ok coursera/hw3 3.897s
```

Запуск:
* `go test -v` - чтобы проверить что ничего не сломалось
* `go test -bench . -benchmem` - для просмотра производительности

Советы:
* Смотрите где мы аллоцируем память
* Смотрите где мы накапливаем весь результат, хотя нам все значения одновременно не нужны
* Смотрите где происходят преобразования типов, которые можно избежать
* Смотрите не только на графе, но и в pprof в текстовом виде (list FastSearch) - там прямо по исходнику можно увидеть где что
* Задание предполагает использование easyjson. На сервере эта библиотека есть, подключать можно. Но сгенерированный через easyjson код вам надо поместить в файл с вашей функцией
* Можно сделать без easyjson

Примечание:
* easyjson основан на рефлекции и не может работать с пакетом main. Для генерации кода вам необходимо вынести вашу структуру в отдельный пакет, сгенерить там код, потом забрать его в main
Binary file added hw3_bench/hw3_bench.test
Binary file not shown.
Loading