{"id":345412,"date":"2023-02-14T09:03:07","date_gmt":"2023-02-14T09:03:07","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=345412"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=345412","title":{"rendered":"<span>\u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043f\u043e\u0431\u0435\u0434\u044b \u0432 VK Cup&#8217;22\/23:Go<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! 5 \u0444\u0435\u0432\u0440\u0430\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0441\u044f \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0439 <a href=\"https:\/\/vk.com\/@vkteam-cup\" rel=\"noopener noreferrer nofollow\">VK Cup<\/a>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u0432\u043f\u0435\u0440\u0432\u044b\u0435 \u0431\u044b\u043b\u0430 \u0441\u0435\u043a\u0446\u0438\u044f \u043f\u043e\u0441\u0432\u044f\u0449\u0451\u043d\u043d\u0430\u044f Go. \u041e \u043a\u043e\u043d\u043a\u0443\u0440\u0441\u0435 \u044f \u0443\u0437\u043d\u0430\u043b \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e \u0432 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u0422\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u043a\u0430\u043d\u0430\u043b\u043e\u0432 \u0438 \u0440\u0435\u0448\u0438\u043b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u0447\u0442\u043e \u0436\u0435 \u0442\u0430\u043c \u0437\u0430 \u0437\u0430\u0434\u0430\u0447\u0438. \u0421\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043b\u043e \u0438\u0437 \u0438\u0437 3 \u044d\u0442\u0430\u043f\u043e\u0432:<\/p>\n<ol>\n<li>\n<p><strong>\u041a\u0432\u0430\u043b\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f:<\/strong> \u043d\u0443\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0448\u043b\u0438 \u0442\u0435\u0441\u0442\u044b. \u0414\u0430\u043b\u044c\u0448\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u043b\u043e 256 \u0447\u0435\u043b\u043e\u0432\u0435\u043a.<\/p>\n<\/li>\n<li>\n<p><strong>\u041e\u0442\u0431\u043e\u0440:<\/strong> \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u0440\u043e \u0432\u043d\u0435\u0448\u043d\u044e\u044e \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0443 \u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 \u043a\u0443\u0447\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043d\u0435 \u0432\u043c\u0435\u0449\u0430\u0435\u0442\u0441\u044f \u0432 RAM. \u0414\u0430\u043b\u044c\u0448\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u043b\u043e 16 \u0447\u0435\u043b\u043e\u0432\u0435\u043a.<\/p>\n<\/li>\n<li>\n<p><strong>\u0424\u0438\u043d\u0430\u043b<\/strong>: \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 \u043a\u043e\u043b\u043b\u0430\u0436\u0430 \u0438\u0437 1000+ \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c 512\u0445512 px.<\/p>\n<\/li>\n<\/ol>\n<p>\u041f\u0440\u043e \u043f\u0435\u0440\u0432\u044b\u0435 2 \u0440\u0430\u0443\u043d\u0434\u0430 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0435 \u0431\u0443\u0434\u0443, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u044e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0441\u0442\u0430\u0442\u044c\u044e, \u0430 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u0440\u043e \u0444\u0438\u043d\u0430\u043b \u0438 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u0440\u0438\u043d\u0435\u0441\u043b\u043e \u043f\u043e\u0431\u0435\u0434\u0443. \u041a\u043e\u0434 \u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u0432\u0441\u0435\u0445 \u0440\u0430\u0443\u043d\u0434\u043e\u0432 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 <a href=\"https:\/\/github.com\/sergei-svistunov\/vkcup2022\" rel=\"noopener noreferrer nofollow\">GitHub<\/a>&#8216;\u0435.<\/p>\n<p>\u0412 \u0444\u0438\u043d\u0430\u043b\u0435 \u0431\u044b\u043b\u0438 \u0447\u0451\u0442\u043a\u0438\u0439 \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u0439 \u043e\u0446\u0435\u043d\u043a\u0438: \u043a\u0442\u043e \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442 \u043a\u043e\u043b\u043b\u0430\u0436, \u0442\u043e\u0442 \u0438 \u043f\u043e\u0431\u0435\u0434\u0438\u043b. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 &#171;\u0432 \u043b\u043e\u0431&#187; \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 \u0437\u0430 ~16 \u0441\u0435\u043a\u0443\u043d\u0434 \u043d\u0430 \u043c\u043e\u0451\u043c AMD Ryzen 7 5800H (16 HT cores). \u0415\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u043a\u0430\u043a \u0435\u0433\u043e \u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c \u0434\u043e 0.23 \u0441\u0435\u043a\u0443\u043d\u0434\u044b, \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442, \u0442\u0430\u043c \u043c\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430, \u043a\u043e\u0434\u0430, \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0438 \u0434\u0430\u0436\u0435 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0430\u0441\u0441\u0435\u043c\u0431\u043b\u0435\u0440\u0430.<\/p>\n<hr\/>\n<h2>\u0417\u0430\u0434\u0430\u0447\u0430<\/h2>\n<p>\u0412\u0430\u043c \u0434\u0430\u043d \u0441\u0430\u0439\u0442. \u0427\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0435\u0433\u043e \u043d\u0430 \u043f\u043e\u0440\u0442\u0443 8080, \u0441\u043a\u0430\u0447\u0430\u0439\u0442\u0435 \u0434\u043e\u043a\u0435\u0440 \u043e\u0431\u0440\u0430\u0437 <code>vkcup2022\/golang:latest<\/code> \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u0435\u0433\u043e \u0441\u0442\u0440\u043e\u0447\u043a\u043e\u0439\u00a0<\/p>\n<pre><code class=\"bash\">docker run -it --rm -d -p 8080:80 --name web vkcup2022\/golang:latest<\/code><\/pre>\n<p>\u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u0435\u0433\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u0434\u043d\u043e \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c 512\u202f\u00d7\u202f512 \u043f\u0438\u043a\u0441\u0435\u043b\u0435\u0439 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 PNG, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043e\u0434\u043d\u0430 \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0441\u044b\u043b\u043e\u043a. \u041e\u0431\u043e\u0439\u0434\u0438\u0442\u0435 \u0433\u0440\u0430\u0444 \u0441\u0430\u0439\u0442\u0430 \u0432 \u0448\u0438\u0440\u0438\u043d\u0443.<\/p>\n<p>\u0412 \u043a\u0430\u0436\u0434\u043e\u0439 \u0438\u0437 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u0435 Red- \u0438 Green-\u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0438\u043a\u0441\u0435\u043b\u044f \u043d\u0430 0 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043e\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e Blue-\u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0430\u044f.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u0435 \u0432\u0441\u0435 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0432 \u0435\u0434\u0438\u043d\u044b\u0439 \u043a\u043e\u043b\u043b\u0430\u0436 \u0438\u043c\u0435\u043d\u043d\u043e \u0432 \u0442\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u043d\u0438 \u0432\u0441\u0442\u0440\u0435\u0442\u0438\u043b\u0438\u0441\u044c \u0432\u0430\u043c \u043f\u0440\u0438 \u043e\u0431\u0445\u043e\u0434\u0435 \u0432 \u0448\u0438\u0440\u0438\u043d\u0443. \u0428\u0438\u0440\u0438\u043d\u0430 \u043a\u043e\u043b\u043b\u0430\u0436\u0430 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f: 32 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f (512\u202f\u00d7\u202f32 = 16\u202f384 \u043f\u0438\u043a\u0441\u0435\u043b\u044f). \u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u043a\u043e\u043b\u043b\u0430\u0436 \u0441\u043b\u0435\u0432\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043e, \u0430 \u043f\u043e\u0442\u043e\u043c \u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437.<\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435: \u043d\u0430 \u0438\u0442\u043e\u0433\u043e\u0432\u043e\u043c \u0441\u0430\u0439\u0442\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0435\u043d\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439. \u041d\u043e \u0432\u0430\u0448\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435 \u0431\u043e\u043b\u0435\u0435 2 \u0413\u0431\u0430\u0439\u0442 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438.<\/p>\n<p>\u0412\u0430\u0448\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0431\u043e\u043b\u0435\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435.\u00a0<strong>\u0427\u0435\u043c \u043c\u0435\u043d\u044c\u0448\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0432\u0430\u0448\u0435\u043c\u0443 \u0440\u0435\u0448\u0435\u043d\u0438\u044e \u043d\u0430 \u0437\u0430\u043f\u0443\u0441\u043a<\/strong>, \u0442\u0435\u043c \u0432\u044b\u0448\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u0430\u0448\u0435 \u043c\u0435\u0441\u0442\u043e \u043d\u0430 \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0438.\u00a0\u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0432\u0430\u0448\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0441\u0442\u0440\u043e\u043a\u043e\u0439<\/p>\n<pre><code class=\"bash\">.\/solution url result.png<\/code><\/pre>\n<h2>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 &#171;\u0432 \u043b\u043e\u0431&#187;<\/h2>\n<pre><code class=\"go\">package main  import ( \"bytes\" \"container\/list\" \"flag\" \"fmt\" \"image\" \"image\/color\" \"image\/png\" \"io\" \"net\/http\" \"net\/url\" \"os\" \"regexp\" \"time\" )  const ( collageWidth = 32 imageWidth   = 512 imageHeight  = 512 )  var ( reImg  = regexp.MustCompile(`&lt;img src=\"(\/[^\"]+.png)\"\/>`) reLink = regexp.MustCompile(`&lt;a href=\"([^\"]+)\">`) )  func main() { flag.Parse()  start := time.Now()  if len(flag.Args()) != 2 { fatalf(\"usage %s &lt;url> &lt;result.png>\", os.Args[0]) } addr := flag.Arg(0) resFile := flag.Arg(1)  fmt.Printf(\"Start crawling from %s and storing into %s\\n\", addr, resFile)  u, err := url.Parse(addr) if err != nil { fatalf(\"%v\", err) }  visited := map[string]struct{}{} queue := list.New() queue.PushBack(u.Path)  cnt := 0  resImg := image.NewRGBA(image.Rect(0, 0, imageWidth*collageWidth, imageHeight*32))  for queue.Len() > 0 { location := queue.Front().Value.(string) queue.Remove(queue.Front())  if _, exists := visited[location]; exists { continue } visited[location] = struct{}{}  links, images, err := getHtmlData(u.Scheme + \":\/\/\" + u.Host + location) if err != nil { fatalf(\"%v\", err) }  for _, link := range links { queue.PushBack(link) }  for _, imgSrc := range images { img, err := getImage(u.Scheme + \":\/\/\" + u.Host + imgSrc) if err != nil { fatalf(\"%v\", err) }  for x := 0; x &lt; imageWidth; x++ { for y := 0; y &lt; imageHeight; y++ { _, _, b, a := img.At(x, y).RGBA() resImg.Set((cnt%collageWidth)*imageWidth+x, (cnt\/collageWidth)*imageHeight+y, color.RGBA{0, 0, uint8(b), uint8(a)}) } }  cnt++ } }  f, err := os.Create(resFile) if err != nil { fatalf(\"%s\", err) } defer f.Close() if err := png.Encode(f, resImg); err != nil { fatalf(\"%s\", err) }  fmt.Printf(\"Processed %d file in %s\\n\", cnt, time.Now().Sub(start)) }  func getHtmlData(url string) ([]string, []string, error) { resp, err := http.Get(url) if err != nil { return nil, nil, err } if resp.StatusCode != http.StatusOK { return nil, nil, fmt.Errorf(\"http error: %d\", resp.StatusCode) }  buf := bytes.Buffer{} if _, err := io.Copy(&amp;buf, resp.Body); err != nil { fatalf(\"%v\", err) }  var links, images []string  for _, link := range reLink.FindAllStringSubmatch(buf.String(), -1) { links = append(links, link[1]) }  for _, img := range reImg.FindAllStringSubmatch(buf.String(), -1) { images = append(images, img[1]) }  return links, images, nil }  func getImage(url string) (image.Image, error) { resp, err := http.Get(url) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf(\"http error: %d\", resp.StatusCode) }  return png.Decode(resp.Body) }  func fatalf(format string, v ...any) { _, _ = fmt.Fprintf(os.Stderr, \"Error: %s\\n\", fmt.Sprintf(format, v...)) os.Exit(255) }<\/code><\/pre>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0434\u043e\u043b\u0433\u043e, \u043f\u043e\u0447\u0442\u0438 16 \u0441\u0435\u043a\u0443\u043d\u0434. \u0421\u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0431\u0443\u0434\u0435\u0442 \u043d\u0435 1024 \u0448\u0442., \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0441\u0441\u044b\u043b\u043e\u043a \u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0441\u043b\u043e\u043c\u0430\u044e\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0445\u043e\u0442\u044f \u0431\u044b 1 \u043f\u0440\u043e\u0431\u0435\u043b. \u041d\u0430\u0434\u043e \u043c\u0435\u043d\u044f\u0442\u044c.<\/p>\n<h2>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430<\/h2>\n<p>\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430 3 \u0431\u043b\u043e\u043a\u0430:<\/p>\n<ol>\n<li>\n<p><strong>Crawler<\/strong>: HTTP \u043a\u043b\u0438\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043c\u0435\u0435\u0442:<\/p>\n<ol>\n<li>\n<p>\u0421\u043a\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432 \u043d\u0443\u0436\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u043d\u0430 \u043d\u0438\u0445 \u0441\u0441\u044b\u043b\u043a\u0438 \u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p>\u0421\u043a\u0430\u0447\u0438\u0432\u0430\u0442\u044c PNG \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0438 \u043f\u0430\u0440\u0441\u0438\u0442\u044c \u0438\u0445.<\/p>\n<\/li>\n<\/ol>\n<\/li>\n<li>\n<p><strong>PNG Copier<\/strong>: \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432 \u043a\u043e\u043b\u043b\u0430\u0436. \u042f\u0434\u0435\u0440 CPU \u0443 \u043d\u0430\u0441 \u043c\u043d\u043e\u0433\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0438 Copier&#8217;\u043e\u0432 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u043c\u043d\u043e\u0433\u043e.<\/p>\n<\/li>\n<li>\n<p><strong>PNG Writer<\/strong>: \u0442.\u043a. \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0438\u0440\u0443\u044e\u0449\u0430\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043d\u0435 \u0432\u043b\u0435\u0437\u0430\u0435\u0442 \u0432 RAM, \u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0443\u043c\u0435\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0432\u044b\u0445\u043e\u0434\u043d\u043e\u0439 \u0444\u0430\u0439\u043b \u0447\u0430\u043d\u043a\u0430\u043c\u0438.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u0441\u0451 \u044d\u0442\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e. \u041e\u0431\u0449\u0435\u043d\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u0447\u0430\u0441\u0442\u044f\u043c\u0438 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u043d\u0430\u043b\u044b. \u041d\u0438\u0436\u0435 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0442\u043e\u043a\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f50\/5f3\/4d5\/f505f34d56fe37a4e5d4e92248aba77b.png\" width=\"1257\" height=\"270\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f50\/5f3\/4d5\/f505f34d56fe37a4e5d4e92248aba77b.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043a\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043a\u0430\u0436\u0434\u0430\u044f \u0438\u0437 \u0447\u0430\u0441\u0442\u0435\u0439 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e.<\/p>\n<h2>Crawler<\/h2>\n<p>\u0415\u0433\u043e \u0437\u0430\u0434\u0430\u0447\u0430 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u043e\u0431\u043e\u0439\u0442\u0438 \u0432\u0435\u0441\u044c \u0441\u0430\u0439\u0442 \u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u0432 \u043a\u0430\u043d\u0430\u043b \u0432\u0441\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435. \u041a\u0430\u043d\u0430\u043b \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u0431\u0443\u0444\u0435\u0440\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u043d\u0430 1024 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 (\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0432 \u0442\u0435\u0441\u0442\u0441\u0435\u0442\u0435).<\/p>\n<p>\u041f\u043e\u0440\u044f\u0434\u043e\u043a \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043a\u0430\u043a \u043f\u0440\u0438 \u043f\u043e\u0438\u0441\u043a\u0435 \u0432 \u0448\u0438\u0440\u0438\u043d\u0443, \u0437\u043d\u0430\u0447\u0438\u0442 \u0432\u0441\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0430 \u043d\u0435 \u0432 \u0441\u0442\u0435\u043a. <\/p>\n<p>\u041f\u043e\u043a\u0430 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043d\u0435 \u043f\u0443\u0441\u0442\u0430, \u0432\u044b\u043d\u0438\u043c\u0430\u0435\u043c \u0438\u0437 \u0433\u043e\u043b\u043e\u0432\u044b URL \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0438 \u043f\u0430\u0440\u0441\u0438\u043c HTML, \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u0445\u0432\u043e\u0441\u0442 \u0441\u043f\u0438\u0441\u043a\u0430, URL \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432 \u043a\u0430\u043d\u0430\u043b \u0434\u043b\u044f Copier&#8217;\u043e\u0432. \u0412 \u0441\u043e\u043a\u0440\u0430\u0449\u0451\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"go\">visited := map[string]struct{}{} queue := list.New() queue.PushBack(u.Path)  buf := bytes.Buffer{}  for queue.Len() > 0 { location := queue.Front().Value.(string) queue.Remove(queue.Front())  if _, exists := visited[location]; exists { continue } visited[location] = struct{}{}  resp, err := c.httpClient.Get(u.Scheme + \":\/\/\" + u.Host + location) \/\/ ... \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a, \u0447\u0442\u0435\u043d\u0438\u0435 HTML \u0432 buf        links, images, err := parseHTML(buf.String()) if err != nil { return err }  for _, link := range links { queue.PushBack(link) }  for _, img := range images { imgCh &lt;- ImgTask{c.id, u.Scheme + \":\/\/\" + u.Host + img} } }<\/code><\/pre>\n<p>\u0412\u043e\u043e\u0431\u0449\u0435 \u043f\u043e\u043b\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0443\u043c\u0435\u0435\u0442 \u043d\u0435 \u0445\u043e\u0434\u0438\u0442\u044c \u0437\u0430 HTML \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u0441\u0430\u0439\u0442\u044b \u0438 \u043f\u043e\u043d\u0438\u043c\u0430\u0435\u0442 \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u044b\u0435 \u0438 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438. \u0421 \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c\u044e 99,9% \u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u043c \u043d\u0430\u0431\u043e\u0440\u0435 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432 \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0431\u044b\u043b\u043e, \u043d\u043e \u043d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b.<\/p>\n<p>\u041e\u0434\u043d\u0430 \u0438\u0437 \u0432\u0430\u0436\u043d\u0435\u0439\u0448\u0438\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0437\u0434\u0435\u0441\u044c \u044d\u0442\u043e <code>parseHTML()<\/code>. \u041e\u0434\u043d\u0430 \u0438\u0437 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432 \u0431\u044b\u043b\u0430 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c RegExp&#8217;\u044b, \u0430 \u043c\u043e\u0436\u043d\u043e \u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0438\u043b\u0438 \u043d\u0435\u0442, \u044f, \u0447\u0435\u0441\u0442\u043d\u043e \u0433\u043e\u0432\u043e\u0440\u044f, \u043d\u0435 \u0437\u043d\u0430\u044e \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440. \u0412 \u043b\u044e\u0431\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0441\u0442\u0440\u043e\u0438\u0442\u044c DOM \u043d\u0430\u043c \u043d\u0435 \u043d\u0430\u0434\u043e, \u0430 \u043d\u0443\u0436\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u043d\u0430\u0439\u0442\u0438 \u043d\u0443\u0436\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e, \u0434\u0430 \u0438 \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434\u044b \u044f \u043b\u044e\u0431\u043b\u044e. \u041e\u043f\u044b\u0442 \u0443\u0447\u0430\u0441\u0442\u0438\u044f \u0432 HighLoad Cup&#8217;\u0430\u0445 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u0441 \u0438\u0445 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u043e\u0436\u043d\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u0440\u0435\u0448\u0438\u0442\u044c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443, \u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043d\u0443\u0436\u043d\u0430 \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u041c\u043e\u0439 \u043f\u0430\u0440\u0441\u0435\u0440 \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 \u0447\u0443\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 100 \u0441\u0442\u0440\u043e\u043a, \u043d\u043e \u0441\u043e \u0441\u0432\u043e\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0435\u0439 \u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u043f\u043b\u044e\u0441 \u0443\u043c\u0435\u0435\u0442 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0438 \u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445:<\/p>\n<pre><code class=\"go\">func parseHTML(s string) ([]string, []string, error) { images := make([]string, 0, 1) links := make([]string, 0, 2)  for len(s) > 0 { \/\/ Find HTML tag start pos := strings.IndexByte(s, '&lt;') if pos == -1 { break } s = s[pos+1:]  s = skipSpaces(s)  \/\/ Find end of token pos = strings.IndexAny(s, \" \\t\\n\\r>\") if pos == -1 { return nil, nil, errors.New(\"invalid html\") }  token := s[:pos] s = s[pos:]  \/\/ Skip comments if token == \"!--\" { pos := strings.Index(s, \"-->\") if pos == -1 { break } s = s[pos+3:] continue }  \/\/ Find end of HTML tag pos = strings.IndexByte(s, '>') if pos == -1 { return nil, nil, errors.New(\"invalid html\") } attrs := s[:pos] s = s[pos+1:]  if attrs != \"\" { switch toLower(token) { case \"a\": pos := strings.Index(attrs, \"href\") if pos != -1 { attrs = skipSpaces(attrs[pos+4:]) if attrs[0] == '=' { attrs = skipSpaces(attrs[1:]) } if attrs[0] != '\"' { return nil, nil, errors.New(\"invalid html\") }  pos = strings.IndexByte(attrs[1:], '\"') if pos == -1 { return nil, nil, errors.New(\"invalid html\") } if pos > 0 { links = append(links, attrs[1:pos+1]) } } case \"img\": pos := strings.Index(attrs, \"src\") if pos != -1 { attrs = skipSpaces(attrs[pos+3:]) if attrs[0] == '=' { attrs = skipSpaces(attrs[1:]) } if attrs[0] != '\"' { return nil, nil, errors.New(\"invalid html\") }  pos = strings.IndexByte(attrs[1:], '\"') if pos == -1 { return nil, nil, errors.New(\"invalid html\") } if pos > 0 { images = append(images, attrs[1:pos+1]) } } } } }  return links, images, nil }  func skipSpaces(s string) string { for len(s) > 0 { switch s[0] { case ' ', '\\t', '\\r', '\\n': s = s[1:] default: return s } }  return s }  func toLower(s string) string { b := []byte(s) for i, c := range b { if c >= 'A' &amp;&amp; c &lt;= 'Z' { b[i] = c - 'A' + 'a' } }  return string(b) }<\/code><\/pre>\n<p><code>toLower()<\/code> \u0437\u0430\u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0432\u043e\u0439, \u0442.\u043a. \u0432 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u043c \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a, \u0430 \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043b\u0438\u0448\u043d\u0438\u0445 \u043d\u0430\u043d\u043e\u0441\u0435\u043a\u0443\u043d\u0434 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043f\u043e\u043c\u0435\u0448\u0430\u0435\u0442. \u041d\u0438\u0436\u0435 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435:<\/p>\n<pre><code>BenchmarkStdToLower-16    30112750        40.81 ns\/op       8 B\/op       1 allocs\/op BenchmarkToLower-16       55108370        33.11 ns\/op       8 B\/op       1 allocs\/op<\/code><\/pre>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435 \u043f\u0430\u0440\u0441\u0435\u0440 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u044b\u043c \u0438 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0431\u0435\u0437 \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043f\u0430\u043c\u044f\u0442\u0438 \u0432 \u043a\u0443\u0447\u0435, \u0435\u0441\u043b\u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u0442\u044c \u0441 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u043c\u0438 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043c\u0438, \u0442\u043e<\/p>\n<pre><code>BenchmarkRegExp-16          440782      4176 ns\/op     723 B\/op      10 allocs\/op BenchmarkParser-16         1986980      595.2 ns\/op      48 B\/op       2 allocs\/op<\/code><\/pre>\n<p>\u041c\u043d\u0435 \u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u0447\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0430\u043a\u0435\u0442\u0430 <code>bytes<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>strings<\/code> \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0435\u0433\u043e \u0435\u0449\u0451 \u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c, \u043d\u043e \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0435\u0435. \u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e benchmark \u043d\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u0441\u044f. \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u0438\u0434\u0435\u0438 \u043f\u043e\u0447\u0435\u043c\u0443, \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u0435\u0441\u044c \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445.<\/p>\n<p>\u0415\u0449\u0451 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u0432 \u0432 Crawler&#8217;\u0435, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0433\u043e\u0448\u043d\u043e\u0433\u043e HTTP \u043a\u043b\u0438\u0435\u043d\u0442\u0430:<\/p>\n<pre><code class=\"go\">httpClient: &amp;http.Client{     Transport: &amp;http.Transport{         DisableCompression:  true,         MaxIdleConnsPerHost: runtime.NumCPU() * 2,         IdleConnTimeout:     time.Minute,         ReadBufferSize:      64 * 1024,     }, },<\/code><\/pre>\n<p>\u0421\u0436\u0430\u0442\u0438\u0435 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e, \u0442.\u043a. HTML \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439, \u0441\u0435\u0442\u044c \u0431\u044b\u0441\u0442\u0440\u0430\u044f. \u0422\u0430\u0439\u043c\u0430\u0443\u0442 \u0434\u043b\u044f \u043e\u0436\u0438\u0434\u0430\u044e\u0449\u0438\u0445 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 &#8212; \u043c\u0438\u043d\u0443\u0442\u0430, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u043b \u0440\u0430\u043d\u044c\u0448\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u0418 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0448\u0442\u0440\u0438\u0445 &#8212; \u0440\u0430\u0437\u043c\u0435\u0440 \u0431\u0443\u0444\u0435\u0440\u0430 \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f, \u0432\u0441\u0435 HTML \u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0445\u043e\u0447\u0435\u0442\u0441\u044f \u0447\u0438\u0442\u0430\u0442\u044c \u0437\u0430 1 <code>Read<\/code>. <\/p>\n<p>\u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u0432\u0441\u0451 \u044d\u0442\u043e \u043d\u0438\u043a\u0430\u043a\u043e\u0439 \u0440\u043e\u043b\u0438 \u043d\u0435 \u0438\u0433\u0440\u0430\u0435\u0442, \u043a\u0430\u043a \u0438 \u0440\u0430\u0441\u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u0438\u0432\u0430\u043d\u0438\u0435 \u043e\u0431\u043a\u0430\u0447\u043a\u0438, \u0442.\u043a. \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043c\u0430\u043b\u043e, \u0432\u0441\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 \u0447\u0443\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 100\u043c\u0441.<\/p>\n<h2>PNG Writer<\/h2>\n<p>\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 encoder \u0443\u043c\u0435\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443 \u0446\u0435\u043b\u0438\u043a\u043e\u043c, \u0430 \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 \u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d \u0444\u043e\u0440\u043c\u0430\u0442 PNG. \u041d\u0430 \u0441\u0430\u0439\u0442\u0435 LibPNG \u0435\u0441\u0442\u044c \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0449\u0430\u044f <a href=\"http:\/\/www.libpng.org\/pub\/png\/spec\/1.2\/PNG-Structure.html\" rel=\"noopener noreferrer nofollow\">\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443<\/a> \u0444\u0430\u0439\u043b\u0430 \u0438 \u043e\u043d\u0430 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430:<\/p>\n<ul>\n<li>\n<p>\u0421\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0430 <code>137 80 78 71 13 10 26 10<\/code><\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0447\u0430\u043d\u043a\u043e\u0432, \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437:<\/p>\n<ul>\n<li>\n<p><strong>\u0414\u043b\u0438\u043d\u0430<\/strong> (4 \u0431\u0430\u0439\u0442\u0430)<\/p>\n<\/li>\n<li>\n<p><strong>\u0422\u0438\u043f<\/strong> (4 \u0431\u0430\u0439\u0442\u0430), \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c:<\/p>\n<ul>\n<li>\n<p><code>IHDR<\/code>: Image header (13 \u0431\u0430\u0439\u0442 \u0441 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438)<\/p>\n<\/li>\n<li>\n<p><code>PLTE<\/code>: Palette<\/p>\n<\/li>\n<li>\n<p><code>IDAT<\/code>: Image data (\u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432 \u0441\u0436\u0430\u0442\u043e\u043c \u0432\u0438\u0434\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e zlib)<\/p>\n<\/li>\n<li>\n<p><code>IEND<\/code>: Image trailer<\/p>\n<\/li>\n<li>\n<p>&#8230;<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>\u0414\u0430\u043d\u043d\u044b\u0435<\/strong> (\u0434\u043b\u0438\u043d\u043d\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c \u0441 \u0447\u0438\u0441\u043b\u043e\u043c \u0432 \u043f\u0435\u0440\u0432\u044b\u0445 4 \u0431\u0430\u0439\u0442\u0430\u0445)x<\/p>\n<\/li>\n<li>\n<p><strong>CRC<\/strong> (4 \u0431\u0430\u0439\u0442\u0430)<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><code>IHDR<\/code> \u0447\u0430\u043d\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043f\u0435\u0440\u0432\u044b\u043c, \u0430 <code>IEND<\/code> &#8212; \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u043c. \u0412 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043f\u043e\u043b\u044f:<\/p>\n<ul>\n<li>\n<p>Width: 4 bytes<\/p>\n<\/li>\n<li>\n<p> Height: 4 bytes<\/p>\n<\/li>\n<li>\n<p> Bit depth: 1 byte<\/p>\n<\/li>\n<li>\n<p> Color type: 1 byte<\/p>\n<\/li>\n<li>\n<p> Compression method: 1 byte (\u0432\u0441\u0435\u0433\u0434\u0430 0)<\/p>\n<\/li>\n<li>\n<p> Filter method: 1 byte<\/p>\n<\/li>\n<li>\n<p> Interlace method: 1 byte<\/p>\n<\/li>\n<\/ul>\n<p>\u041e\u0442 \u043d\u043e\u0432\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u0437\u0430\u043f\u0438\u0441\u0438 PNG \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0443\u043c\u0435\u044e\u0449\u0438\u0435:<\/p>\n<ol>\n<li>\n<p>\u0417\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a (\u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0430 + <code>IHDR<\/code>)<\/p>\n<\/li>\n<li>\n<p><strong>\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e<\/strong> \u0440\u0430\u0437 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 (<code>IDAT<\/code>)<\/p>\n<\/li>\n<li>\n<p> \u0417\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u0440\u0438\u0437\u043d\u0430\u043a \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 (<code>IEND<\/code>)<\/p>\n<\/li>\n<\/ol>\n<p>\u0422\u0430\u043a \u043a\u0430\u043a \u0438\u0442\u043e\u0433\u043e\u0432\u0430\u044f \u0432\u044b\u0441\u043e\u0442\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u043d\u0430\u043c \u043d\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430 \u043d\u0430 \u043c\u043e\u043c\u0435\u043d\u0442 \u0437\u0430\u043f\u0438\u0441\u0438 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430, \u0442\u043e \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043b\u044e\u0431\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u0432 \u0432\u044b\u0441\u043e\u0442\u0443, \u0430 \u0432 \u043a\u043e\u043d\u0446\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0435\u0449\u0451 \u0440\u0430\u0437, \u043a \u0441\u0447\u0430\u0441\u0442\u044c\u044e \u043e\u043d \u0438\u043c\u0435\u0435\u0442 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440.<\/p>\n<p>\u0412\u043e\u043e\u0431\u0449\u0435 PNG \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0438\u043c\u0435\u0442\u044c \u043c\u043d\u043e\u0433\u043e <code>IDAT<\/code> \u0447\u0430\u043d\u043a\u043e\u0432, \u0442.\u0435. \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 \u043d\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0447\u0430\u043d\u043a, \u043d\u043e \u044f \u0440\u0435\u0448\u0438\u043b \u0442\u0430\u043a \u043d\u0435 \u0434\u0435\u043b\u0430\u0442\u044c, \u0430 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u044e \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443 \u0432 \u043e\u0434\u043d\u043e\u043c \u0447\u0430\u043d\u043a\u0435. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0431\u044b\u043b \u0432\u044b\u0434\u0435\u043b\u0435\u043d \u0431\u0443\u0444\u0435\u0440, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432 \u0441\u0436\u0430\u0442\u043e\u043c \u0432\u0438\u0434\u0435. \u0414\u0430\u043d\u043d\u044b\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u043f\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u043e, \u043f\u0435\u0440\u0435\u0434 \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u043e\u0432\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u0432\u044b\u0434\u0435\u043b\u0435\u043d 1 \u0431\u0430\u0439\u0442 \u0434\u043b\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u0430, \u0432\u0441\u0435\u0433\u043e \u0438\u0445 5 \u0432\u0438\u0434\u043e\u0432, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u043b\u0443\u0447\u0448\u0435\u0433\u043e \u0441\u0436\u0430\u0442\u0438\u044f, \u043d\u043e \u044f \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u043d\u0438\u043a\u0430\u043a\u0438\u0435. \u0412 \u0438\u0442\u043e\u0433\u0435 \u0443 \u043c\u0435\u043d\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u0434:<\/p>\n<pre><code class=\"go\">package mypng  import ( \"bytes\" \"compress\/zlib\" \"encoding\/binary\" \"errors\" \"hash\/crc32\" \"image\" \"io\" \"strconv\" )  const pngHeader = \"\\x89PNG\\r\\n\\x1a\\n\"  type Encoder struct { w      io.Writer header [8]byte footer [4]byte tmp    [4 * 256]byte  buf *bytes.Buffer zw  *zlib.Writer }  func NewEncoder(w io.Writer) *Encoder { buf := bytes.NewBuffer(make([]byte, 0, 4*1024*1024))  zw := zlib.NewWriter(buf)  return &amp;Encoder{ w:   w, buf: buf, zw:  zw, } }  func (e *Encoder) WriteIHDR(w, h uint32) error { if _, err := io.WriteString(e.w, pngHeader); err != nil { return err }  binary.BigEndian.PutUint32(e.tmp[0:4], w) binary.BigEndian.PutUint32(e.tmp[4:8], h) \/\/ Set bit depth and color type. e.tmp[8] = 8 e.tmp[9] = 6 e.tmp[10] = 0 \/\/ default compression method e.tmp[11] = 0 \/\/ default filter method e.tmp[12] = 0 \/\/ non-interlaced  return e.writeChunk(e.tmp[:13], \"IHDR\") }  func (e *Encoder) WriteIDAT(i *image.NRGBA) error { b := i.Bounds()  for y := 0; y &lt; b.Max.Y; y++ { if _, err := e.zw.Write([]byte{0}); err != nil { return err }  offset := (y - b.Min.Y) * i.Stride if _, err := e.zw.Write(i.Pix[offset : offset+b.Dx()*4]); err != nil { return err } }  return nil }  func (e *Encoder) FinishIDAT() error { if err := e.zw.Close(); err != nil { return err }  return e.writeChunk(e.buf.Bytes(), \"IDAT\") }  func (e *Encoder) WriteIEND() error { return e.writeChunk(nil, \"IEND\") }  func (e *Encoder) writeChunk(b []byte, name string) error { n := uint32(len(b)) if int(n) != len(b) { return errors.New(name + \" chunk is too large: \" + strconv.Itoa(len(b)))  } binary.BigEndian.PutUint32(e.header[:4], n) e.header[4] = name[0] e.header[5] = name[1] e.header[6] = name[2] e.header[7] = name[3] crc := crc32.NewIEEE() _, _ = crc.Write(e.header[4:8]) _, _ = crc.Write(b) binary.BigEndian.PutUint32(e.footer[:4], crc.Sum32())  if _, err := e.w.Write(e.header[:8]); err != nil { return err } if _, err := e.w.Write(b); err != nil { return err } if _, err := e.w.Write(e.footer[:4]); err != nil { return err }  return nil } <\/code><\/pre>\n<p>\u041a\u0430\u043a\u0438\u0435-\u0442\u043e \u0447\u0430\u0441\u0442\u0438 \u0431\u044b\u043b\u0438 \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0438\u0437 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438.<\/p>\n<p>\u0424\u043e\u0440\u043c\u0430\u0442 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 NRGBA \u0445\u0440\u0430\u043d\u0438\u0442 \u043f\u0438\u043a\u0441\u0435\u043b\u044b \u0432 \u0442\u0430\u043a\u043e\u043c \u0436\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0435, \u043a\u0430\u043a \u0438 PNG \u0441 BitDepth=8 \u0438 ColorType=6, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u0446\u0435\u043b\u0438\u043a\u043e\u043c, \u0430 \u043d\u0435 \u043f\u043e\u043f\u0438\u043a\u0441\u0435\u043b\u044c\u043d\u043e.<\/p>\n<p>\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 benchmark, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u0443\u0437\u043a\u0438\u0435 \u043c\u0435\u0441\u0442\u0430:<\/p>\n<pre><code class=\"go\">package mypng_test  import ( \"image\" \"os\" \"testing\"  \"round-3\/mypng\" )  var testNRGBA *image.NRGBA  func init() { testNRGBA = image.NewNRGBA(image.Rect(0, 0, 512*32, 512*32)) for i := 0; i &lt; len(testNRGBA.Pix)\/4; i++ { testNRGBA.Pix[i*4+2] = 255 testNRGBA.Pix[i*4+3] = 255 } }  func BenchmarkWriteRGBA(b *testing.B) { for i := 0; i &lt; b.N; i++ { f, err := os.Create(\"\/tmp\/test.png\") if err != nil { b.Fatal(err) } defer f.Close()  myEnc := mypng.NewEncoder(f) if err := myEnc.WriteIHDR(512*32, 512*32); err != nil { b.Fatal(err) }  if err := myEnc.WriteIDAT(testNRGBA); err != nil { b.Fatal(err) }  if err := myEnc.FinishIDAT(); err != nil { b.Fatal(err) }  if err := myEnc.WriteIEND(); err != nil { b.Fatal(err) } } }<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0435\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0431\u044b\u043b\u0438 \u0442\u0430\u043a\u0438\u0435:<\/p>\n<pre><code>BenchmarkWriteRGBA-16           12314487506 ns\/op<\/code><\/pre>\n<p>2.3 \u0441\u0435\u043a\u0443\u043d\u0434\u044b, \u0431\u0435\u0434\u0430. \u0415\u0441\u043b\u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u043f\u0440\u043e\u0444\u0430\u0439\u043b\u0438\u043d\u0433 CPU, \u0442\u043e \u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u0432\u0441\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043b\u043e\u0436\u0438\u0442\u0441\u044f \u043d\u0430 <code>zlib.Write()<\/code> \u0438 \u0441 \u044d\u0442\u0438\u043c \u043d\u0430\u0434\u043e \u0447\u0442\u043e-\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c.<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ab8\/849\/3b6\/ab88493b6b856da03bea77950fdb0f69.png\" width=\"1807\" height=\"266\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ab8\/849\/3b6\/ab88493b6b856da03bea77950fdb0f69.png\"\/><figcaption><\/figcaption><\/figure>\n<p><strong>\u0418\u0434\u0435\u044f \u21161<\/strong>: zlib \u0438\u043c\u0435\u0435\u0442 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u0442\u0435\u043f\u0435\u043d\u0438 \u0441\u0436\u0430\u0442\u0438\u044f, \u043e\u0434\u043d\u0430 \u0438\u0437 \u043d\u0438\u0445 <code>BestSpeed<\/code>. \u041a\u0430\u0436\u0435\u0442\u0441\u044f \u0442\u043e, \u0447\u0442\u043e \u043d\u0430\u0434\u043e, \u043f\u0440\u043e\u0431\u0443\u0435\u043c:<\/p>\n<pre><code>BenchmarkWriteRGBA-16           2 770471273 ns\/op<\/code><\/pre>\n<p>\u0412 3 \u0440\u0430\u0437\u0430 \u043b\u0443\u0447\u0448\u0435, \u043d\u043e \u0445\u043e\u0447\u0435\u0442\u0441\u044f \u0431\u044b\u0441\u0442\u0440\u0435\u0435. \u041f\u0435\u0440\u0435\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c zlib \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043d\u0435\u0442, \u0434\u0430 \u0438 \u043d\u0435 \u0444\u0430\u043a\u0442, \u0447\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c (\u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u043c\u043e\u0436\u043d\u043e), \u0437\u043d\u0430\u0447\u0438\u0442 \u043d\u0430\u0434\u043e \u0434\u0443\u043c\u0430\u0442\u044c \u043a\u0430\u043a \u0443\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c \u043e\u0431\u044a\u0451\u043c \u0434\u0430\u043d\u043d\u044b\u0445.<\/p>\n<p><strong>\u0418\u0434\u0435\u044f \u21162<\/strong>: \u041d\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0438\u043d\u0438\u0439 \u0446\u0432\u0435\u0442, \u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u043c\u044b \u0432\u0441\u0435 4. PNG \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0444\u043e\u0440\u043c\u0430\u0442 \u0441 \u043f\u0430\u043b\u0438\u0442\u0440\u043e\u0439 \u0438\u0437 256 \u0446\u0432\u0435\u0442\u043e\u0432, \u0447\u0442\u043e \u043d\u0430\u043c \u0438 \u043d\u0430\u0434\u043e. \u041a\u0430\u0436\u0434\u044b\u0439 \u043e\u0442\u0442\u0435\u043d\u043e\u043a \u0441\u0438\u043d\u0435\u0433\u043e \u043e\u0434\u043d\u043e\u0437\u043d\u0430\u0447\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0446\u0432\u0435\u0442\u0443 \u0432 \u043f\u0430\u043b\u0438\u0442\u0440\u0435. \u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c PNG \u0432 \u043d\u043e\u0432\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435, \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0437\u0430\u043f\u0438\u0441\u0438 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u0438 \u0434\u0430\u043d\u043d\u044b\u0445, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043f\u0430\u043b\u0438\u0442\u0440\u044b \u0432 \u0444\u0430\u0439\u043b\u0435:<\/p>\n<pre><code class=\"go\">func (e *Encoder) WriteIHDR(w, h uint32) error { if _, err := io.WriteString(e.w, pngHeader); err != nil { return err }  binary.BigEndian.PutUint32(e.tmp[0:4], w) binary.BigEndian.PutUint32(e.tmp[4:8], h) \/\/ Set bit depth and color type. e.tmp[8] = 8 e.tmp[9] = 3 \/\/ !!! 6 -> 3 e.tmp[10] = 0 \/\/ default compression method e.tmp[11] = 0 \/\/ default filter method e.tmp[12] = 0 \/\/ non-interlaced  return e.writeChunk(e.tmp[:13], \"IHDR\") }  func (e *Encoder) WritePLTE(p color.Palette) error { if len(p) &lt; 1 || len(p) > 256 { return errors.New(\"bad palette length: \" + strconv.Itoa(len(p))) } for i, c := range p { c1 := color.NRGBAModel.Convert(c).(color.NRGBA) e.tmp[3*i+0] = c1.R e.tmp[3*i+1] = c1.G e.tmp[3*i+2] = c1.B }  return e.writeChunk(e.tmp[:3*len(p)], \"PLTE\") }  func (e *Encoder) WriteIDAT(i *image.Paletted) error { b := i.Bounds()  for y := 0; y &lt; b.Max.Y; y++ { if _, err := e.zw.Write([]byte{0}); err != nil { return err }  offset := y * i.Stride  if _, err := e.zw.Write(i.Pix[offset : offset+b.Max.X]); err != nil { return err } }  return nil }<\/code><\/pre>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0432 4 \u0440\u0430\u0437\u0430 \u043c\u0435\u043d\u044c\u0448\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u044d\u0442\u043e \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043d\u0430 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438:<\/p>\n<pre><code>BenchmarkWriteRGBA-16           2 770471273 ns\/op BenchmarkWritePaletted-16           6 181868631 ns\/op<\/code><\/pre>\n<p>\u0412\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0445\u043e\u0440\u043e\u0448\u043e, \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c \u0432\u0441\u044e \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e.<\/p>\n<h2>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043b\u043b\u0430\u0436\u0430<\/h2>\n<p>\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e \u043e\u0434\u043d\u043e\u0439 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0435 \u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u0441 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u044f\u0434\u0440\u0430\u043c\u0438 \u043d\u0435\u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0440\u043e\u0441\u043a\u043e\u0448\u044c \u0432 \u0437\u0430\u0434\u0430\u0447\u0430\u0445 \u0433\u0434\u0435 \u0446\u0435\u043b\u044c &#8212; \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 DataFlow \u0442\u0430\u043a\u043e\u0439:<\/p>\n<ol>\n<li>\n<p>\u0415\u0441\u0442\u044c \u0431\u0443\u0444\u0444\u0435\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b \u0441 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430\u043c\u0438 \u043d\u0430 \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u043a\u0443 \u043a\u043e\u043b\u043b\u0430\u0436\u0430 &#8212; \u0447\u0430\u043d\u043a\u0430\u043c\u0438 (16384&#215;512 px). \u041e\u043d \u043d\u0443\u0436\u0435\u043d \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0432\u044b\u0434\u0435\u043b\u044f\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u043f\u0430\u043c\u044f\u0442\u044c \u043f\u043e\u0434 \u043d\u043e\u0432\u0443\u044e \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443, \u043f\u043b\u044e\u0441 \u043a\u0430\u043d\u0430\u043b \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442, \u0447\u0442\u043e \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u0435 \u0447\u0435\u043c N \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a, \u0432 \u043e\u0442\u043b\u0438\u0447\u0438\u0438 \u043e\u0442 <code>sync.Pool<\/code><\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u0442\u044c N \u0432\u043e\u0440\u043a\u0435\u0440\u043e\u0432, \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 <\/p>\n<ol>\n<li>\n<p>\u0411\u0435\u0440\u0451\u0442 URL \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0438 \u0435\u0451 ID \u0438\u0437 Crawler&#8217;\u0430<\/p>\n<\/li>\n<li>\n<p>\u0421\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u0438 \u0434\u0435\u043a\u043e\u0434\u0438\u0440\u0443\u0435\u0442 PNG<\/p>\n<\/li>\n<li>\n<p>\u0412 \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0441\u044f \u043a \u0442\u0435\u043a\u0443\u0449\u0435\u043c\u0443 \u0447\u0430\u043d\u043a\u0443, \u0442\u043e \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0435\u0451 \u0432 \u043d\u0443\u0436\u043d\u043e\u0435 \u043c\u0435\u0441\u0442\u043e \u0447\u0430\u043d\u043a\u0430, \u0438\u043d\u0430\u0447\u0435 \u043e\u0436\u0438\u0434\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0447\u0430\u043d\u043a \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e SpinLock&#8217;\u0430 (CPU \u043d\u0430\u043c \u043d\u0435 \u0436\u0430\u043b\u043a\u043e, \u0430 \u0432\u0440\u0435\u043c\u044f \u043d\u0430 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u0436\u0430\u043b\u043a\u043e).<\/p>\n<\/li>\n<\/ol>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0441\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0447\u0430\u043d\u043a\u0430 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u044b, \u0442\u043e \u043e\u043d \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 \u0431\u0443\u0444\u0435\u0440\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b, \u043e\u0442\u043a\u0443\u0434\u0430 \u0434\u0440\u0443\u0433\u043e\u0439 \u0433\u043e\u0440\u0443\u0442\u0438\u043d\u043e\u0439 \u043e\u043d \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0432\u044b\u0445\u043e\u0434\u043d\u043e\u0439 \u0444\u0430\u0439\u043b \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 \u043f\u0435\u0440\u0432\u044b\u0439 \u043a\u0430\u043d\u0430\u043b \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<\/li>\n<\/ol>\n<p>\u0413\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438 \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a:<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f74\/553\/6d7\/f745536d7d670a5405192cdb6bf35b19.png\" width=\"1108\" height=\"727\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f74\/553\/6d7\/f745536d7d670a5405192cdb6bf35b19.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0410 \u0432 \u043a\u043e\u0434\u0435 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"go\">rowImgBufCh := make(chan *image.Paletted, rowImgBufSz) for i := 0; i &lt; rowImgBufSz; i++ {     rowImgBufCh &lt;- image.NewPaletted(image.Rect(0, 0, imageWidth*collageWidth, imageHeight), nil) } rowImgCh := make(chan *image.Paletted, rowImgBufSz)  rowImg := &lt;-rowImgBufCh line := 0 leftRowImages := int32(collageWidth)  activeWorkers := int32(workers) for i := 0; i &lt; workers; i++ {     go func() {         for task := range tasksCh {             img, err := c.GetImage(task.Location)             if err != nil {                 fatalf(\"%v\", err)             }              \/\/ Next line spin             for task.Id\/collageWidth > line {             }              copyImage(img, rowImg, (task.Id%collageWidth)*imageWidth)              \/\/ Row is complete             if atomic.AddInt32(&amp;leftRowImages, -1) == 0 {                 rowImgCh &lt;- rowImg                 rowImg = &lt;-rowImgBufCh                 leftRowImages = collageWidth                 line++             }         }          \/\/ All workers are done         if atomic.AddInt32(&amp;activeWorkers, -1) == 0 {             if leftRowImages != collageWidth {                 \/\/ Clean unused space                 for y := 0; y &lt; imageHeight; y++ {                     dstPixOffset := rowImg.PixOffset(int((collageWidth-leftRowImages)*imageWidth), y)                     for x := 0; x &lt; int(leftRowImages)*imageWidth; x++ {                         rowImg.Pix[dstPixOffset+x] = 0                     }                 }                  \/\/ Store partial chunk                 rowImgCh &lt;- rowImg                 line++             }              close(rowImgCh)         }     }() }  for rowImg := range rowImgCh {     if err := enc.WriteIDAT(rowImg); err != nil {         fatalf(\"%v\", err)     }     rowImgBufCh &lt;- rowImg }<\/code><\/pre>\n<p>\u0412\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0431\u0435\u0437 \u043b\u043e\u043a\u043e\u0432, \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u0438\u0434\u0451\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>atomic<\/code> \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439. \u041f\u043e\u0434\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u044f \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0438\u044e \u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0432\u0435\u0447\u0435\u0440 \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0435\u043c \u0440\u0430\u0443\u043d\u0434\u0430, \u044f \u0437\u0430\u043c\u0435\u0442\u0438\u043b, \u0447\u0442\u043e \u043c\u043e\u0439 \u0441\u043f\u0438\u043d\u043b\u043e\u043a \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u0432\u044b\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u043e\u0431\u0449\u0435\u043c \u0444\u043e\u043d\u0435:<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/7b3\/ec4\/26e\/7b3ec426ea3ff3f3765ff0487f9468b4.png\" width=\"1758\" height=\"397\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7b3\/ec4\/26e\/7b3ec426ea3ff3f3765ff0487f9468b4.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0434\u043e\u043b\u0433\u0438\u0445 \u0441\u043e\u043c\u043d\u0435\u043d\u0438\u0439, \u0441\u0442\u043e\u0438\u0442 \u043b\u0438 \u0440\u0438\u0441\u043a\u043e\u0432\u0430\u0442\u044c \u0438 \u0432 \u043f\u044f\u0442\u043d\u0438\u0446\u0443 \u043d\u043e\u0447\u044c\u044e \u043f\u0435\u0440\u0435\u0434\u0435\u043b\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0441\u0440\u0430\u0437\u0443 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0447\u0430\u043d\u043a\u043e\u0432 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e, \u043d\u043e \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043d\u0435 \u043d\u0430\u0440\u0443\u0448\u0438\u0442\u044c \u0438\u0445 \u043f\u043e\u0440\u044f\u0434\u043e\u043a, \u044f \u0440\u0435\u0448\u0438\u043b \u0443\u0431\u0440\u0430\u0442\u044c \u0441\u043f\u0438\u043d\u043b\u043e\u043a \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043a\u0430\u043a\u043e\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u0440\u043e\u0441\u0442 \u043f\u043e \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438. \u041a\u043e\u043d\u0435\u0447\u043d\u043e \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u0440\u0430\u0437\u044a\u0435\u0445\u0430\u043b\u0430\u0441\u044c, \u043d\u043e \u0432\u044b\u0438\u0433\u0440\u044b\u0448 \u0432 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0431\u044b\u043b ~10%, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442 \u043d\u0430\u0434\u043e \u0440\u0438\u0441\u043a\u043d\u0443\u0442\u044c \u0438 \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u0435\u0440\u0435\u0434\u0435\u043b\u0430\u0442\u044c, \u0445\u043e\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0443\u0436\u0435 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0448\u0430\u043d\u0441\u0430 \u0432\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0438\u0441\u043f\u0440\u0430\u0432\u0438\u0442\u044c.<\/p>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0447\u0430\u043d\u043a\u0438 \u044f \u0441\u043b\u043e\u0436\u0438\u043b \u0432 map&#8217;\u0443, \u043f\u043e\u0447\u0435\u043c\u0443 \u0438\u043c\u0435\u043d\u043d\u043e \u0432 \u0442\u0430\u043a\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u044f \u0438 \u0441\u0435\u0439\u0447\u0430\u0441 \u043d\u0435 \u043e\u0442\u0432\u0435\u0447\u0443, \u043d\u043e \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e \u043a\u0430\u043a \u0435\u0441\u0442\u044c. \u041e\u0431\u0435\u0440\u043d\u0443\u043b \u0432\u0441\u0451 \u044d\u0442\u043e RWMutex&#8217;\u043e\u043c \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0433\u043e\u043d\u043e\u043a \u0438 \u043f\u0430\u043d\u0438\u043a\u0438. \u0422\u0430\u043a\u0436\u0435 \u0441\u0447\u0451\u0442\u0447\u0438\u043a \u043d\u0435\u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0445 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u043f\u0435\u0440\u0435\u0435\u0445\u0430\u043b \u0432 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0447\u0430\u043d\u043a\u0435. \u0412 \u0438\u0442\u043e\u0433\u0435 \u043a\u043e\u0434 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u043b\u0441\u044f \u0432:<\/p>\n<pre><code class=\"go\">type chunk struct {     rowImg        *image.Paletted     leftRowImages int32 } chunksMap := map[uint16]*chunk{} chunksMtx := sync.RWMutex{} line := uint16(0)  activeWorkers := int32(workers) for i := 0; i &lt; workers; i++ {     go func() {         for task := range tasksCh {             img, err := c.GetImage(task.Location)             if err != nil {                 fatalf(\"%v\", err)             }              chunkId := uint16(task.Id \/ collageWidth)              chunksMtx.Lock()             curChunk := chunksMap[(chunkId)]             if curChunk == nil {                 curChunk = &amp;chunk{                     rowImg:        &lt;-rowImgBufCh,                     leftRowImages: int32(collageWidth),                 }                 chunksMap[chunkId] = curChunk             }             chunksMtx.Unlock()              copyImage(img, curChunk.rowImg, (task.Id%collageWidth)*imageWidth)              \/\/ Row is complete             if atomic.AddInt32(&amp;curChunk.leftRowImages, -1) == 0 {                 if chunkId != line {                     continue                 }                  \/\/ Store chunk                 rowImgCh &lt;- curChunk.rowImg                 line++                  \/\/ Check if next chunks have finished earlier                 for {                     chunksMtx.RLock()                     c := chunksMap[line]                     chunksMtx.RUnlock()                      if c != nil &amp;&amp; c.leftRowImages == 0 {                         rowImgCh &lt;- c.rowImg                         line++                     } else {                         break                     }                 }             }         }          \/\/ All workers are done         if atomic.AddInt32(&amp;activeWorkers, -1) == 0 {             if lastChunk := chunksMap[line]; lastChunk != nil &amp;&amp; lastChunk.leftRowImages != collageWidth {                 \/\/ Clean unused space                 for y := 0; y &lt; imageHeight; y++ {                     dstPixOffset := lastChunk.rowImg.PixOffset(int((collageWidth-lastChunk.leftRowImages)*imageWidth), y)                     for x := 0; x &lt; int(lastChunk.leftRowImages)*imageWidth; x++ {                         lastChunk.rowImg.Pix[dstPixOffset+x] = 0                     }                 }                  \/\/ Store partial chunk                 rowImgCh &lt;- lastChunk.rowImg                 line++             }              close(rowImgCh)         }     }() }<\/code><\/pre>\n<h2>copyImage()<\/h2>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u0430\u043c\u0430\u044f \u043d\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c, \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u0442\u0435\u043d \u043c\u0438\u043b\u043b\u0438\u043e\u043d\u043e\u0432 \u043f\u0438\u043a\u0441\u0435\u043b\u043e\u0432. <\/p>\n<p>\u0418\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u044f \u0440\u0435\u0448\u0438\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u043c <code>image.Image<\/code> \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0446\u0432\u0435\u0442\u0430 \u043f\u0438\u043a\u0441\u0435\u043b\u0430 \u0438\u0437 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430:<\/p>\n<pre><code class=\"go\">func copyImage(src image.Image, dst *image.Paletted, offsetX int) {     for y := 0; y &lt; imageHeight; y++ {         dstPixOffset := dst.PixOffset(offsetX, y)         for x := 0; x &lt; imageWidth; x++ {             _, _, b, _ := src.At(x, y).RGBA()             dst.Pix[dstPixOffset+x] = uint8(b)         }     } }<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u043f\u0440\u043e\u0444\u0430\u0439\u043b\u0438\u043d\u0433<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f94\/2ae\/23b\/f942ae23b5c41d103bad4a17ca86d551.png\" width=\"1813\" height=\"266\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f94\/2ae\/23b\/f942ae23b5c41d103bad4a17ca86d551.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0442\u043e \u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u0431\u043e\u0301\u043b\u044c\u0448\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 <code>At()<\/code>, \u043f\u043b\u044e\u0441 \u043e\u043d \u0435\u0449\u0451 \u0432\u044b\u0434\u0435\u043b\u044f\u0435\u0442 \u043f\u0430\u043c\u044f\u0442\u044c. \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0437\u0434\u0435\u0441\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u0430, \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043d \u0444\u043e\u0440\u043c\u0430\u0442 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e <code>image.RGBA<\/code>, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u0431\u0430\u0432\u0438\u0442\u044c\u0441\u044f \u043e\u0442 \u0432\u044b\u0437\u043e\u0432\u0430 <code>At()<\/code> \u0438 \u0441\u0440\u0430\u0437\u0443 \u0434\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0434\u043e \u043d\u0443\u0436\u043d\u043e\u0433\u043e \u043f\u0438\u043a\u0441\u0435\u043b\u0430, \u0430 \u0442\u043e\u0447\u043d\u0435\u0435 \u0435\u0433\u043e \u0441\u0438\u043d\u0435\u0439 \u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0435\u0439. \u042f \u043d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u043e\u0441\u0442\u0430\u0432\u0438\u043b \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442, \u0435\u0441\u043b\u0438 \u0432\u0441\u0451 \u0442\u0430\u043a\u0438 \u043f\u0440\u0438\u0434\u0451\u0442 \u043d\u0435 <code>image.RGBA<\/code>:<\/p>\n<pre><code class=\"go\">func copyImage(src image.Image, dst *image.Paletted, offsetX int) { switch src := src.(type) { case *image.RGBA: for y := 0; y &lt; imageHeight; y++ { for x := 0; x &lt; imageWidth; x++ { dst.Pix[dstPixOffset+x] = src.Pix[srcPixOffset+x*4+1] } }  default: for y := 0; y &lt; imageHeight; y++ { dstPixOffset := dst.PixOffset(offsetX, y) for x := 0; x &lt; imageWidth; x++ { _, _, b, _ := src.At(x, y).RGBA() dst.Pix[dstPixOffset+x] = uint8(b) } } } }<\/code><\/pre>\n<p>\u0421\u0442\u0430\u043b\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u043b\u0443\u0447\u0448\u0435:<\/p>\n<pre><code>BenchmarkCopySTD-16         274   4326141 ns\/op 1048586 B\/op  262144 allocs\/op BenchmarkCopyPix-16        5012    213639 ns\/op       0 B\/op       0 allocs\/op<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0430\u0441\u0441\u0435\u043c\u0431\u043b\u0435\u0440\u0430, \u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441\u043b\u0430\u0439\u0441\u0430 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 &#8212; \u044d\u0442\u043e \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043d\u043e \u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u043d\u0435\u0432\u044b\u0445\u043e\u0434 \u0437\u0430 \u0440\u0430\u0437\u043c\u0435\u0440\u044b \u0441\u043b\u0430\u0439\u0441\u0430 (Bounds Check).<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/539\/3d1\/f62\/5393d1f625aca4905220086fe05f65a0.png\" width=\"824\" height=\"517\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/539\/3d1\/f62\/5393d1f625aca4905220086fe05f65a0.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041b\u044e\u0431\u043e\u0439 \u043b\u0438\u0448\u043d\u0438\u0439 \u0442\u0430\u043a\u0442 CPU \u043d\u0430 \u0442\u0430\u043a\u0438\u0445 \u043e\u0431\u044a\u0451\u043c\u0430\u0445 \u043e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u0435\u043d, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u0434\u043e \u0438\u0437\u0431\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043e\u0442 \u043d\u0435\u0451. Bounds Check \u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0438 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u0438, \u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u044d\u0442\u0438\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u044f \u043d\u0435 \u043c\u043e\u0433\u0443, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0443\u0436\u0435\u043d \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u0418 \u0437\u0434\u0435\u0441\u044c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0447\u0451\u0440\u043d\u0430\u044f \u043c\u0430\u0433\u0438\u044f unsafe:<\/p>\n<pre><code class=\"go\">dstAddr := uintptr(unsafe.Pointer(&amp;dst.Pix[0])) srcAddr := uintptr(unsafe.Pointer(&amp;src.Pix[0])) for y := 0; y &lt; imageHeight; y++ {     srcPixOffset := src.PixOffset(0, y)     dstPixOffset := dst.PixOffset(offsetX, y)     for x := 0; x &lt; imageWidth; x++ {         \/\/dst.Pix[dstPixOffset+x] = src.Pix[srcPixOffset+x*4+1]         dstPtr := (*uint8)((unsafe.Pointer)(dstAddr + uintptr(dstPixOffset+x)))         srcPtr := (*uint8)((unsafe.Pointer)(srcAddr + uintptr(srcPixOffset+x*4+2)))         *dstPtr = *srcPtr     } }<\/code><\/pre>\n<p>\u0412 \u0441\u0442\u0440\u043e\u043a\u0430\u0445 (1) \u0438 (2) \u0434\u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u0430\u0434\u0440\u0435\u0441 \u043d\u0443\u043b\u0435\u0432\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0441\u043b\u0430\u0439\u0441\u043e\u0432, \u0438 \u0442\u0443\u0442 Bounds Check \u0432\u0441\u0451 \u0435\u0449\u0451 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043d\u043e \u0442.\u043a. \u043e\u043d \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e 1 \u0440\u0430\u0437, \u0442\u043e \u043d\u0435 \u0441\u0442\u0440\u0430\u0448\u043d\u043e. \u0410 \u0434\u0430\u043b\u044c\u0448\u0435 \u0432 \u0441\u0442\u0440\u043e\u043a\u0430\u0445 (8) \u0438 (9) \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u0438 \u043d\u0430 \u043d\u0443\u0436\u043d\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0438 \u0432 \u0441\u0442\u0440\u043e\u043a\u0435 (10) \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435. \u042d\u0442\u043e \u0434\u0430\u0451\u0442 \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432 1.5 \u0440\u0430\u0437\u0430 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0441\u043b\u0430\u0439\u0441\u0430. \u041f\u0440\u0443\u0444:<\/p>\n<pre><code>BenchmarkCopySTD-16         274   4326141 ns\/op 1048586 B\/op  262144 allocs\/op BenchmarkCopyPix-16        5012    213639 ns\/op       0 B\/op       0 allocs\/op BenchmarkCopyPixUnsafe-16   8310    142169 ns\/op       0 B\/op       0 allocs\/op<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u0430\u0441\u0441\u0435\u043c\u0431\u043b\u0435\u0440, \u0442\u043e \u0432\u0438\u0434\u043d\u043e \u043a\u0430\u043a \u043e\u043d \u043f\u043e\u0445\u0443\u0434\u0435\u043b:<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/e2c\/89f\/937\/e2c89f937aa81f5427b3f7f26ab6bdf7.png\" width=\"909\" height=\"392\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e2c\/89f\/937\/e2c89f937aa81f5427b3f7f26ab6bdf7.png\"\/><figcaption><\/figcaption><\/figure>\n<h2>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 PNG Decoder<\/h2>\n<p>\u0412 \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u0432\u0438\u0434\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0442\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0437\u0430 ~450ms. CPU Profile \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0442\u0440\u0430\u0442\u0438\u0442 \u043d\u0430 \u043f\u0430\u0440\u0441\u0438\u043d\u0433 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 PNG \u0444\u0430\u0439\u043b\u043e\u0432, \u043f\u043b\u044e\u0441 \u0437\u0430\u043c\u0435\u0442\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0442\u0440\u0430\u0442\u0438\u0442\u0441\u044f \u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u043f\u0430\u043c\u044f\u0442\u0438 (\u0432\u044b\u0434\u0435\u043b\u0438\u043b \u0441\u0442\u0440\u0435\u043b\u043a\u043e\u0439):<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/431\/c96\/43e\/431c9643ebd4a4e1afdbade0c75528fb.png\" width=\"1748\" height=\"397\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/431\/c96\/43e\/431c9643ebd4a4e1afdbade0c75528fb.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0412\u043e\u043e\u0431\u0449\u0435 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 PNG \u0432 \u043f\u0430\u043c\u044f\u0442\u044c \u0438\u0437 \u0441\u043e\u043a\u0435\u0442\u0430<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0443 \u0438 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a PNG<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u043e \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0438 \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0435 \u0432 \u043f.3<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c CRC PNG \u0447\u0430\u043d\u043a\u0430<\/p>\n<\/li>\n<\/ol>\n<\/li>\n<li>\n<p>\u0421\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0438\u0437 \u043f.3 \u0432 \u0447\u0430\u043d\u043a \u043a\u043e\u043b\u043b\u0430\u0436\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u0430\u043d \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0444\u0430\u0439\u043b (<code>copyImage()<\/code>)<\/p>\n<\/li>\n<\/ol>\n<p>\u041e\u0447\u0435\u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u0431\u0430\u0432\u0438\u0442\u044c\u0441\u044f \u043e\u0442 \u043b\u0438\u0448\u043d\u0435\u0433\u043e \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043f\u0430\u043c\u044f\u0442\u0438 \u0438 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u043f\u0443\u043d\u043a\u0442\u0430\u0445 3-5, \u0430 \u0437\u0430\u043e\u0434\u043d\u043e \u043d\u0435 \u0442\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u043d\u0430 \u043f\u043e\u0434\u0441\u0447\u0451\u0442 CRC, \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c \u0447\u0442\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0442\u043e\u0440\u044b \u043f\u043e\u0434\u0441\u0443\u043d\u0443\u0442 \u0431\u0438\u0442\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u0442\u0440\u0435\u043c\u0438\u0442\u0441\u044f \u043a 0.<\/p>\n<p>\u041f\u0438\u0441\u0430\u0442\u044c \u0434\u0435\u043a\u043e\u0434\u0435\u0440 \u043f\u043e\u0434 \u0432\u0441\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u044b PNG \u043d\u0435 \u043d\u0430\u0434\u043e, \u043d\u0443\u0436\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430, \u043d\u043e \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0445 \u0441\u044e\u0440\u043f\u0440\u0438\u0437\u043e\u0432 \u044f \u0441\u0434\u0435\u043b\u0430\u043b <code>Sniffer<\/code> &#8212; \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 <code>io.Reader<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0445\u0440\u0430\u043d\u0438\u0442 \u0432 \u0441\u0435\u0431\u0435 \u043f\u0435\u0440\u0432\u044b\u0435 33 \u0431\u0430\u0439\u0442\u0430, \u0447\u0442\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0434\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u043d\u0443\u0436\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 PNG \u043f\u0440\u0438\u0448\u0451\u043b \u0438\u043b\u0438 \u043d\u0435\u0442. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0430, Sniffer \u043c\u043e\u0436\u043d\u043e \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0432 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438 \u043e\u0442\u0434\u0430\u0442\u044c \u0435\u0433\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u043c\u0443 <code>png.Decoder<\/code>.<\/p>\n<pre><code class=\"go\">package mypng  import ( \"io\" )  const bufSize = 33  type Sniffer struct { r    io.Reader buf  [bufSize]byte left int }  func NewSniffer(r io.Reader) *Sniffer { return &amp;Sniffer{ r:    r, left: -1, } }  func (s *Sniffer) Read(p []byte) (n int, err error) { if s.left == -1 { n, err := io.ReadFull(s.r, s.buf[:]) if err != nil { return 0, err } s.left = n }  if s.left == 0 { return s.r.Read(p) }  if len(p) &lt;= s.left { copy(p, s.buf[bufSize-s.left:]) s.left -= len(p) return len(p), err }  copy(p, s.buf[bufSize-s.left:]) n, err = s.r.Read(p[s.left:]) if err != nil { return n, err } n += s.left s.left = 0  return n, nil }  func (s *Sniffer) Restart() { s.left = bufSize }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043d\u044f\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0434\u0435\u043a\u043e\u0434\u0435\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443 \u043f\u0438\u043a\u0441\u0435\u043b\u0435\u0439 \u043f\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0443. \u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439:<\/p>\n<ol>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 PNG \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u043f\u0430\u043c\u044f\u0442\u044c \u0438\u0437 \u0441\u043e\u043a\u0435\u0442\u0430<\/p>\n<\/li>\n<li>\n<p>\u0427\u0438\u0442\u0430\u0435\u043c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a PNG \u0444\u0430\u0439\u043b\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Sniffer<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043d\u0435\u0437\u043d\u0430\u043a\u043e\u043c\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442, \u0442\u043e <\/p>\n<ol>\n<li>\n<p>\u0421\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c Sniffer \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u0438\u0437 \u043d\u0435\u0433\u043e \u043d\u0438\u043a\u0442\u043e \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0447\u0438\u0442\u0430\u043b<\/p>\n<\/li>\n<li>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0438\u0437 <code>png.Decode<\/code><\/p>\n<\/li>\n<\/ol>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u043d\u043e\u0432\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0442\u0438\u043f\u0430 RawData, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u0438\u043c\u0438\u043a\u0440\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u0434 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>image.Image<\/code> \u0438 \u0443\u043c\u0435\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u043f\u0438\u043a\u0441\u0435\u043b\u043e\u0432 \u043f\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0412 \u043c\u043e\u043c\u0435\u043d\u0442 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>copyImage()<\/code> \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0443 \u043f\u0438\u043a\u0441\u0435\u043b\u043e\u0432, \u0432 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u043a\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0433\u043e \u0447\u0438\u0441\u043b\u0430 \u0431\u0430\u0439\u0442 \u0432 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0431\u0443\u0444\u0444\u0435\u0440, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 PNG \u0444\u0438\u043b\u044c\u0442\u0440\u044b (\u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u0431\u044b\u043b\u0430 \u0447\u0435\u0441\u0442\u043d\u043e \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0438\u0437 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430 png) \u0438 \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0447\u0430\u043d\u043a \u043a\u043e\u043b\u043b\u0430\u0436\u0430.<\/p>\n<\/li>\n<\/ol>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>copyImage()<\/code> \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c \u0435\u0449\u0451 1 \u0432\u0435\u0442\u043a\u0430 \u0432 <code>switch<\/code>:<\/p>\n<pre><code class=\"go\">case *mypng.RawData:     dstAddr := uintptr(unsafe.Pointer(&amp;dst.Pix[0]))     for y := 0; y &lt; imageHeight; y++ {         srcPix := src.Next()         srcAddr := uintptr(unsafe.Pointer(&amp;srcPix[0]))         dstPixOffset := dst.PixOffset(offsetX, y)         for x := 0; x &lt; imageWidth; x++ {             \/\/dst.Pix[dstPixOffset+x] = src.Pix[srcPixOffset+x*4+1]             dstPtr := (*uint8)((unsafe.Pointer)(dstAddr + uintptr(dstPixOffset+x)))             srcPtr := (*uint8)((unsafe.Pointer)(srcAddr + uintptr(x*3+2)))             *dstPtr = *srcPtr         }     }     mypng.RawDataPool.Put(src)<\/code><\/pre>\n<p>\u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u044a\u0435\u043a\u0442 RawData \u0431\u044b\u043b \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d, \u043e\u043d \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 <code>Pool<\/code> \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f, \u0447\u0442\u043e \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 \u0432\u0440\u0435\u043c\u044f \u043d\u0430 \u043b\u0438\u0448\u043d\u0438\u0435 \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043f\u0430\u043c\u044f\u0442\u0438.<\/p>\n<p>\u0422\u0430\u043a\u043e\u0439 \u0444\u043e\u043a\u0443\u0441 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442\u044c \u0435\u0449\u0451 \u043f\u043e\u0440\u044f\u0434\u043a\u0430 130ms (1.4 \u0440\u0430\u0437\u0430). \u041a\u043e\u0434 PNG \u0434\u0435\u043a\u043e\u0434\u0435\u0440\u0430 \u0431\u0435\u0437 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>filterPaeth<\/code> (\u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0438\u0437 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430 <code>png<\/code>) \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"go\">package mypng  import ( \"bytes\" \"compress\/zlib\" \"encoding\/binary\" \"errors\" \"fmt\" \"image\" \"image\/color\" \"image\/png\" \"io\" \"sync\" )  var RawDataPool = &amp;sync.Pool{ New: func() any { return &amp;RawData{ Buffer: bytes.NewBuffer(make([]byte, 0, 8*1024)), } }, }  var discardBuf [8 * 1024]byte  type RawData struct { *bytes.Buffer cr, pr [512*3 + 1]byte zr     io.ReadCloser }  func (r *RawData) ColorModel() color.Model { panic(\"implement me\") } func (r *RawData) Bounds() image.Rectangle { panic(\"implement me\") } func (r *RawData) At(x, y int) color.Color { panic(\"implement me\") }  func (r *RawData) Reset() { r.Buffer.Reset() if r.zr != nil { r.zr.Close() r.zr = nil } }  func (r *RawData) Next() []byte { const bytesPerPixel = 3  if r.zr == nil { r.zr, _ = zlib.NewReader(r.Buffer) }  \/\/ Read the decompressed bytes. _, err := io.ReadFull(r.zr, r.cr[:]) if err != nil { panic(err) }  \/\/ Apply the filter. cdat := r.cr[1:] pdat := r.pr[1:] switch r.cr[0] { case 0: \/\/ No-op. case 1: for i := bytesPerPixel; i &lt; len(cdat); i++ { cdat[i] += cdat[i-bytesPerPixel] } case 2: for i, p := range pdat { cdat[i] += p } case 3: \/\/ The first column has no column to the left of it, so it is a \/\/ special case. We know that the first column exists because we \/\/ check above that width != 0, and so len(cdat) != 0. for i := 0; i &lt; bytesPerPixel; i++ { cdat[i] += pdat[i] \/ 2 } for i := bytesPerPixel; i &lt; len(cdat); i++ { cdat[i] += uint8((int(cdat[i-bytesPerPixel]) + int(pdat[i])) \/ 2) } case 4: filterPaeth(cdat, pdat, bytesPerPixel) default: panic(fmt.Errorf(\"bad filter type %d\", r.cr[0])) }  r.pr, r.cr = r.cr, r.pr  return cdat }  type pngIHDR struct { Width             uint32 Height            uint32 BitDepth          uint8 ColorType         uint8 CompressionMethod uint8 FilterMethod      uint8 InterlaceMethod   uint8 }  func Decode(r io.Reader) (image.Image, error) { sr := NewSniffer(r) hdr, err := readIHDR(sr) if err != nil { return nil, err }  if hdr.Width != 512 || hdr.Height != 512 { return nil, errors.New(\"invalid PNG size\") }  if hdr.BitDepth != 8 || hdr.ColorType != 2 || hdr.CompressionMethod != 0 || hdr.FilterMethod != 0 || hdr.InterlaceMethod != 0 { sr.Restart() return png.Decode(sr) }  chunkId, length, err := readChunkHeader(sr) if chunkId != \"IDAT\" { return nil, errors.New(\"invalid PNG\") }  rawData := RawDataPool.Get().(*RawData) rawData.Reset() if _, err := io.CopyN(rawData, sr, int64(length)); err != nil { return nil, err }  \/\/ Skip all rest data for { if _, err := sr.Read(discardBuf[:]); err != nil { if err == io.EOF { break } return nil, err } }  return rawData, nil }  func readIHDR(r io.Reader) (pngIHDR, error) { var tmp [17]byte  \/\/ Read signature _, err := io.ReadFull(r, tmp[:8]) if err != nil { return pngIHDR{}, err }  id, length, err := readChunkHeader(r) if id != \"IHDR\" || length != 13 { return pngIHDR{}, errors.New(\"invalid PNG\") }  \/\/ Read header data if _, err := io.ReadFull(r, tmp[:17]); err != nil { return pngIHDR{}, err }  return pngIHDR{ Width:             binary.BigEndian.Uint32(tmp[0:4]), Height:            binary.BigEndian.Uint32(tmp[4:8]), BitDepth:          tmp[8], ColorType:         tmp[9], CompressionMethod: tmp[10], FilterMethod:      tmp[11], InterlaceMethod:   tmp[12], }, nil }  func readChunkHeader(r io.Reader) (string, uint32, error) { var tmp [8]byte if _, err := io.ReadFull(r, tmp[:]); err != nil { return \"\", 0, err }  return string(tmp[4:8]), binary.BigEndian.Uint32(tmp[:4]), nil }<\/code><\/pre>\n<p>\u0421\u0435\u0439\u0447\u0430\u0441 CPU Profile \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b1f\/01a\/757\/b1f01a757ec5d580560f95017c646b59.png\" width=\"1748\" height=\"397\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b1f\/01a\/757\/b1f01a757ec5d580560f95017c646b59.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0417\u0434\u0435\u0441\u044c \u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u0431\u043e\u0301\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0442\u0440\u0430\u0442\u0438\u0442\u0441\u044f \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u0438 \u0440\u0430\u0441\u043f\u0430\u043a\u043e\u0432\u043a\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e zlib. \u0421 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u043c\u0438 \u044f \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0440\u0438\u0434\u0443\u043c\u0430\u043b, \u0430 \u0432\u043e\u0442 \u0434\u043b\u044f zlib \u043d\u0430\u0448\u043b\u043e\u0441\u044c \u0440\u0435\u0448\u0435\u043d\u0438\u0435.<\/p>\n<h2>\u0412\u043d\u0435\u0448\u043d\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438<\/h2>\n<p>\u041a\u0430\u043a \u044f \u0443\u0436\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u043b \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 \u0441\u0442\u0430\u0442\u044c\u0438, \u044f \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440 \u043d\u0435 \u0437\u043d\u0430\u044e \u043c\u043e\u0436\u043d\u043e \u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0438\u043b\u0438 \u043d\u0435\u0442, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b 2 \u0440\u0435\u0448\u0435\u043d\u0438\u044f: \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u043c\u0438 \u0438 \u0431\u0435\u0437. \u041a\u0430\u043a\u043e\u0435 \u0438\u0437 \u043d\u0438\u0445 \u043f\u043e\u0431\u0435\u0434\u0438\u043b\u043e &#8212; \u043d\u0435 \u0437\u043d\u0430\u044e ?<\/p>\n<p>\u0417\u0430\u043d\u0438\u043c\u0430\u0442\u044c\u0441\u044f \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0435\u0439 zlib \u043d\u0435 \u0431\u044b\u043b\u043e \u043d\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043d\u0438 \u0441\u0438\u043b, \u043d\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u044f. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043f\u043e\u0448\u0451\u043b \u0438\u0441\u043a\u0430\u0442\u044c, \u043c\u043e\u0436\u0435\u0442 \u043a\u0442\u043e-\u0442\u043e \u0443\u0436\u0435 \u0440\u0435\u0448\u0430\u043b \u0434\u0430\u043d\u043d\u0443\u044e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443. \u0418 \u043a\u0430\u043a \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0435\u0441\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0447\u0435\u043b\u043e\u0432\u0435\u043a, \u043d\u0430\u0448\u043b\u0430\u0441\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"http:\/\/github.com\/klauspost\/compress\" rel=\"noopener noreferrer nofollow\">github.com\/klauspost\/compress<\/a> \u0438 \u043e\u043d\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430 zlib. \u0417\u0430\u043e\u0434\u043d\u043e \u044f \u0440\u0435\u0448\u0438\u043b \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 <code>http.Client<\/code> \u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0438\u0437 <a href=\"http:\/\/github.com\/valyala\/fasthttp\" rel=\"noopener noreferrer nofollow\">github.com\/valyala\/fasthttp<\/a>. <\/p>\n<p>\u0418 \u044d\u0442\u043e \u043f\u0440\u0438\u043d\u0435\u0441\u043b\u043e \u0435\u0449\u0451 \u043f\u043e\u0447\u0442\u0438 20% \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0438\u044f. \u0412 \u0438\u0442\u043e\u0433\u0435 16 \u0441\u0435\u043a\u0443\u043d\u0434 \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u043b\u0438\u0441\u044c \u0432 0.23 \u0441\u0435\u043a\u0443\u043d\u0434\u044b.<\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u043e\u0444\u0430\u0439\u043b\u0435\u0440\u0430 \u0438\u0442\u043e\u0433\u043e\u0432\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0442\u0430\u043a:<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8d6\/c53\/cd0\/8d6c53cd065ca8042c1818c10f57b1b0.png\" alt=\"CPU\" title=\"CPU\" width=\"1748\" height=\"397\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8d6\/c53\/cd0\/8d6c53cd065ca8042c1818c10f57b1b0.png\"\/><figcaption>CPU<\/figcaption><\/figure>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/bd2\/b16\/7e9\/bd2b167e9282964379f25a172baa5c09.png\" alt=\"Memory: allocated objects\" title=\"Memory: allocated objects\" width=\"1748\" height=\"327\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/bd2\/b16\/7e9\/bd2b167e9282964379f25a172baa5c09.png\"\/><figcaption>Memory: allocated objects<\/figcaption><\/figure>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/9a9\/0a1\/1d4\/9a90a11d4da926105d858c0a63068b18.png\" alt=\"Memory: allocated space\" title=\"Memory: allocated space\" width=\"1748\" height=\"327\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9a9\/0a1\/1d4\/9a90a11d4da926105d858c0a63068b18.png\"\/><figcaption>Memory: allocated space<\/figcaption><\/figure>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0421\u043f\u0430\u0441\u0438\u0431\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0442\u043e\u0440\u0430\u043c \u043a\u043e\u043d\u043a\u0443\u0440\u0441\u0430 \u0438 \u0432\u0441\u0435\u043c \u043a\u0442\u043e \u0434\u043e\u0447\u0438\u0442\u0430\u043b \u044d\u0442\u043e\u0442 \u043b\u043e\u043d\u0433\u0440\u0438\u0434 \u0434\u043e \u043a\u043e\u043d\u0446\u0430. \u0415\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0430 \u0442\u0435\u043c\u0430 \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0439 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0435 \u043d\u0430 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0443 <a href=\"https:\/\/highload.fun\/\" rel=\"noopener noreferrer nofollow\">HighLoad.Fun<\/a> \u0438 \u0432\u043b\u0438\u0432\u0430\u0439\u0442\u0435\u0441\u044c \u0432 \u043d\u0430\u0448\u0435 \u043a\u043e\u043c\u044c\u044e\u043d\u0438\u0442\u0438 \u043b\u044e\u0431\u0438\u0442\u0435\u043b\u0435\u0439 \u0431\u044b\u0441\u0442\u0440\u044b\u0445 &#171;\u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434\u043e\u0432&#187;. \u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u043a\u043e\u043d\u043a\u0443\u0440\u0441\u0435.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p> <!----> <!----><\/div>\n<p> <!----> <\/p>\n<div class=\"tm-article-poll\">\n<div class=\"tm-notice tm-article-poll__notice tm-notice_positive\"><!----> <\/p>\n<div class=\"tm-notice__inner\"><!----> <\/p>\n<div class=\"tm-notice__content\"><span>\u0422\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043c\u043e\u0433\u0443\u0442 \u0443\u0447\u0430\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432 \u043e\u043f\u0440\u043e\u0441\u0435. <a rel=\"nofollow\" href=\"\/kek\/v1\/auth\/habrahabr\/?back=\/ru\/post\/716292\/&#038;hl=ru\">\u0412\u043e\u0439\u0434\u0438\u0442\u0435<\/a>, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430.<\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"tm-article-poll__header\">\u0423\u0434\u0430\u043b\u043e\u0441\u044c \u0434\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0434\u043e \u043a\u043e\u043d\u0446\u0430?<\/div>\n<div class=\"tm-article-poll__answers\">\n<div class=\"tm-article-poll__answer\">\n<div class=\"tm-article-poll__answer-data\"><span class=\"tm-article-poll__answer-percent tm-article-poll__answer-percent_winning\">             100%           <\/span> <span class=\"tm-article-poll__answer-label\">\u0414\u0430<\/span> <span class=\"tm-article-poll__answer-votes\">             6           <\/span><\/div>\n<div class=\"tm-article-poll__answer-bar\">\n<div class=\"tm-article-poll__answer-progress tm-article-poll__answer-progress_winning\" style=\"width:100%;\"><\/div>\n<\/div>\n<\/div>\n<div class=\"tm-article-poll__answer\">\n<div class=\"tm-article-poll__answer-data\"><span class=\"tm-article-poll__answer-percent\">             0%           <\/span> <span class=\"tm-article-poll__answer-label\">\u041d\u0435\u0442<\/span> <span class=\"tm-article-poll__answer-votes\">             0           <\/span><\/div>\n<div class=\"tm-article-poll__answer-bar\">\n<div class=\"tm-article-poll__answer-progress\" style=\"width:0%;\"><\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"tm-article-poll__stats\">        \u041f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0430\u043b\u0438 6 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439.          \u0412\u043e\u0437\u0434\u0435\u0440\u0436\u0430\u043b\u0438\u0441\u044c 2 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.      <\/div>\n<\/div>\n<div class=\"tm-article-poll\">\n<div class=\"tm-notice tm-article-poll__notice tm-notice_positive\"><!----> <\/p>\n<div class=\"tm-notice__inner\"><!----> <\/p>\n<div class=\"tm-notice__content\"><span>\u0422\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043c\u043e\u0433\u0443\u0442 \u0443\u0447\u0430\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432 \u043e\u043f\u0440\u043e\u0441\u0435. <a rel=\"nofollow\" href=\"\/kek\/v1\/auth\/habrahabr\/?back=\/ru\/post\/716292\/&#038;hl=ru\">\u0412\u043e\u0439\u0434\u0438\u0442\u0435<\/a>, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430.<\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"tm-article-poll__header\">\u041f\u0438\u0441\u0430\u0442\u044c \u043f\u0440\u043e \u0437\u0430\u0434\u0430\u0447\u0443 \u0438\u0437 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0440\u0430\u0443\u043d\u0434\u0430?<\/div>\n<div class=\"tm-article-poll__answers\">\n<div class=\"tm-article-poll__answer\">\n<div class=\"tm-article-poll__answer-data\"><span class=\"tm-article-poll__answer-percent tm-article-poll__answer-percent_winning\">             100%           <\/span> <span class=\"tm-article-poll__answer-label\">\u0414\u0430<\/span> <span class=\"tm-article-poll__answer-votes\">             5           <\/span><\/div>\n<div class=\"tm-article-poll__answer-bar\">\n<div class=\"tm-article-poll__answer-progress tm-article-poll__answer-progress_winning\" style=\"width:100%;\"><\/div>\n<\/div>\n<\/div>\n<div class=\"tm-article-poll__answer\">\n<div class=\"tm-article-poll__answer-data\"><span class=\"tm-article-poll__answer-percent\">             0%           <\/span> <span class=\"tm-article-poll__answer-label\">\u041d\u0435\u0442<\/span> <span class=\"tm-article-poll__answer-votes\">             0           <\/span><\/div>\n<div class=\"tm-article-poll__answer-bar\">\n<div class=\"tm-article-poll__answer-progress\" style=\"width:0%;\"><\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"tm-article-poll__stats\">        \u041f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0430\u043b\u0438 5 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439.          \u0412\u043e\u0437\u0434\u0435\u0440\u0436\u0430\u043b\u0438\u0441\u044c 2 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.      <\/div>\n<\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/716292\/\"> https:\/\/habr.com\/ru\/post\/716292\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! 5 \u0444\u0435\u0432\u0440\u0430\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0441\u044f \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0439 <a href=\"https:\/\/vk.com\/@vkteam-cup\" rel=\"noopener noreferrer nofollow\">VK Cup<\/a>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u0432\u043f\u0435\u0440\u0432\u044b\u0435 \u0431\u044b\u043b\u0430 \u0441\u0435\u043a\u0446\u0438\u044f \u043f\u043e\u0441\u0432\u044f\u0449\u0451\u043d\u043d\u0430\u044f Go. \u041e \u043a\u043e\u043d\u043a\u0443\u0440\u0441\u0435 \u044f \u0443\u0437\u043d\u0430\u043b \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e \u0432 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u0422\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u043a\u0430\u043d\u0430\u043b\u043e\u0432 \u0438 \u0440\u0435\u0448\u0438\u043b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u0447\u0442\u043e \u0436\u0435 \u0442\u0430\u043c \u0437\u0430 \u0437\u0430\u0434\u0430\u0447\u0438. \u0421\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043b\u043e \u0438\u0437 \u0438\u0437 3 \u044d\u0442\u0430\u043f\u043e\u0432:<\/p>\n<ol>\n<li>\n<p><strong>\u041a\u0432\u0430\u043b\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f:<\/strong> \u043d\u0443\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0448\u043b\u0438 \u0442\u0435\u0441\u0442\u044b. \u0414\u0430\u043b\u044c\u0448\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u043b\u043e 256 \u0447\u0435\u043b\u043e\u0432\u0435\u043a.<\/p>\n<\/li>\n<li>\n<p><strong>\u041e\u0442\u0431\u043e\u0440:<\/strong> \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u0440\u043e \u0432\u043d\u0435\u0448\u043d\u044e\u044e \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0443 \u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 \u043a\u0443\u0447\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043d\u0435 \u0432\u043c\u0435\u0449\u0430\u0435\u0442\u0441\u044f \u0432 RAM. \u0414\u0430\u043b\u044c\u0448\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u043b\u043e 16 \u0447\u0435\u043b\u043e\u0432\u0435\u043a.<\/p>\n<\/li>\n<li>\n<p><strong>\u0424\u0438\u043d\u0430\u043b<\/strong>: \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 \u043a\u043e\u043b\u043b\u0430\u0436\u0430 \u0438\u0437 1000+ \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c 512\u0445512 px.<\/p>\n<\/li>\n<\/ol>\n<p>\u041f\u0440\u043e \u043f\u0435\u0440\u0432\u044b\u0435 2 \u0440\u0430\u0443\u043d\u0434\u0430 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0435 \u0431\u0443\u0434\u0443, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u044e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0441\u0442\u0430\u0442\u044c\u044e, \u0430 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u0440\u043e \u0444\u0438\u043d\u0430\u043b \u0438 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u0440\u0438\u043d\u0435\u0441\u043b\u043e \u043f\u043e\u0431\u0435\u0434\u0443. \u041a\u043e\u0434 \u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u0432\u0441\u0435\u0445 \u0440\u0430\u0443\u043d\u0434\u043e\u0432 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 <a href=\"https:\/\/github.com\/sergei-svistunov\/vkcup2022\" rel=\"noopener noreferrer nofollow\">GitHub<\/a>&#8216;\u0435.<\/p>\n<p>\u0412 \u0444\u0438\u043d\u0430\u043b\u0435 \u0431\u044b\u043b\u0438 \u0447\u0451\u0442\u043a\u0438\u0439 \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u0439 \u043e\u0446\u0435\u043d\u043a\u0438: \u043a\u0442\u043e \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442 \u043a\u043e\u043b\u043b\u0430\u0436, \u0442\u043e\u0442 \u0438 \u043f\u043e\u0431\u0435\u0434\u0438\u043b. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 &#171;\u0432 \u043b\u043e\u0431&#187; \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 \u0437\u0430 ~16 \u0441\u0435\u043a\u0443\u043d\u0434 \u043d\u0430 \u043c\u043e\u0451\u043c AMD Ryzen 7 5800H (16 HT cores). \u0415\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u043a\u0430\u043a \u0435\u0433\u043e \u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c \u0434\u043e 0.23 \u0441\u0435\u043a\u0443\u043d\u0434\u044b, \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442, \u0442\u0430\u043c \u043c\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430, \u043a\u043e\u0434\u0430, \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0438 \u0434\u0430\u0436\u0435 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0430\u0441\u0441\u0435\u043c\u0431\u043b\u0435\u0440\u0430.<\/p>\n<hr\/>\n<h2>\u0417\u0430\u0434\u0430\u0447\u0430<\/h2>\n<p>\u0412\u0430\u043c \u0434\u0430\u043d \u0441\u0430\u0439\u0442. \u0427\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0435\u0433\u043e \u043d\u0430 \u043f\u043e\u0440\u0442\u0443 8080, \u0441\u043a\u0430\u0447\u0430\u0439\u0442\u0435 \u0434\u043e\u043a\u0435\u0440 \u043e\u0431\u0440\u0430\u0437 <code>vkcup2022\/golang:latest<\/code> \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 \u0435\u0433\u043e \u0441\u0442\u0440\u043e\u0447\u043a\u043e\u0439\u00a0<\/p>\n<pre><code class=\"bash\">docker run -it --rm -d -p 8080:80 --name web vkcup2022\/golang:latest<\/code><\/pre>\n<p>\u041d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u0435\u0433\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u0434\u043d\u043e \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c 512\u202f\u00d7\u202f512 \u043f\u0438\u043a\u0441\u0435\u043b\u0435\u0439 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 PNG, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043e\u0434\u043d\u0430 \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0441\u044b\u043b\u043e\u043a. \u041e\u0431\u043e\u0439\u0434\u0438\u0442\u0435 \u0433\u0440\u0430\u0444 \u0441\u0430\u0439\u0442\u0430 \u0432 \u0448\u0438\u0440\u0438\u043d\u0443.<\/p>\n<p>\u0412 \u043a\u0430\u0436\u0434\u043e\u0439 \u0438\u0437 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u0435 Red- \u0438 Green-\u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0438\u043a\u0441\u0435\u043b\u044f \u043d\u0430 0 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043e\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e Blue-\u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0430\u044f.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u0435 \u0432\u0441\u0435 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0432 \u0435\u0434\u0438\u043d\u044b\u0439 \u043a\u043e\u043b\u043b\u0430\u0436 \u0438\u043c\u0435\u043d\u043d\u043e \u0432 \u0442\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u043d\u0438 \u0432\u0441\u0442\u0440\u0435\u0442\u0438\u043b\u0438\u0441\u044c \u0432\u0430\u043c \u043f\u0440\u0438 \u043e\u0431\u0445\u043e\u0434\u0435 \u0432 \u0448\u0438\u0440\u0438\u043d\u0443. \u0428\u0438\u0440\u0438\u043d\u0430 \u043a\u043e\u043b\u043b\u0430\u0436\u0430 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f: 32 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f (512\u202f\u00d7\u202f32 = 16\u202f384 \u043f\u0438\u043a\u0441\u0435\u043b\u044f). \u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u043a\u043e\u043b\u043b\u0430\u0436 \u0441\u043b\u0435\u0432\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u043e, \u0430 \u043f\u043e\u0442\u043e\u043c \u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437.<\/p>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435: \u043d\u0430 \u0438\u0442\u043e\u0433\u043e\u0432\u043e\u043c \u0441\u0430\u0439\u0442\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0435\u043d\u044c \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439. \u041d\u043e \u0432\u0430\u0448\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435 \u0431\u043e\u043b\u0435\u0435 2 \u0413\u0431\u0430\u0439\u0442 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438.<\/p>\n<p>\u0412\u0430\u0448\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0431\u043e\u043b\u0435\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435.\u00a0<strong>\u0427\u0435\u043c \u043c\u0435\u043d\u044c\u0448\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0432\u0430\u0448\u0435\u043c\u0443 \u0440\u0435\u0448\u0435\u043d\u0438\u044e \u043d\u0430 \u0437\u0430\u043f\u0443\u0441\u043a<\/strong>, \u0442\u0435\u043c \u0432\u044b\u0448\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u0430\u0448\u0435 \u043c\u0435\u0441\u0442\u043e \u043d\u0430 \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0438.\u00a0\u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0432\u0430\u0448\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0441\u0442\u0440\u043e\u043a\u043e\u0439<\/p>\n<pre><code class=\"bash\">.\/solution url result.png<\/code><\/pre>\n<h2>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 &#171;\u0432 \u043b\u043e\u0431&#187;<\/h2>\n<pre><code class=\"go\">package main  import ( \"bytes\" \"container\/list\" \"flag\" \"fmt\" \"image\" \"image\/color\" \"image\/png\" \"io\" \"net\/http\" \"net\/url\" \"os\" \"regexp\" \"time\" )  const ( collageWidth = 32 imageWidth   = 512 imageHeight  = 512 )  var ( reImg  = regexp.MustCompile(`&lt;img src=\"(\/[^\"]+.png)\"\/>`) reLink = regexp.MustCompile(`&lt;a href=\"([^\"]+)\">`) )  func main() { flag.Parse()  start := time.Now()  if len(flag.Args()) != 2 { fatalf(\"usage %s &lt;url> &lt;result.png>\", os.Args[0]) } addr := flag.Arg(0) resFile := flag.Arg(1)  fmt.Printf(\"Start crawling from %s and storing into %s\\n\", addr, resFile)  u, err := url.Parse(addr) if err != nil { fatalf(\"%v\", err) }  visited := map[string]struct{}{} queue := list.New() queue.PushBack(u.Path)  cnt := 0  resImg := image.NewRGBA(image.Rect(0, 0, imageWidth*collageWidth, imageHeight*32))  for queue.Len() > 0 { location := queue.Front().Value.(string) queue.Remove(queue.Front())  if _, exists := visited[location]; exists { continue } visited[location] = struct{}{}  links, images, err := getHtmlData(u.Scheme + \":\/\/\" + u.Host + location) if err != nil { fatalf(\"%v\", err) }  for _, link := range links { queue.PushBack(link) }  for _, imgSrc := range images { img, err := getImage(u.Scheme + \":\/\/\" + u.Host + imgSrc) if err != nil { fatalf(\"%v\", err) }  for x := 0; x &lt; imageWidth; x++ { for y := 0; y &lt; imageHeight; y++ { _, _, b, a := img.At(x, y).RGBA() resImg.Set((cnt%collageWidth)*imageWidth+x, (cnt\/collageWidth)*imageHeight+y, color.RGBA{0, 0, uint8(b), uint8(a)}) } }  cnt++ } }  f, err := os.Create(resFile) if err != nil { fatalf(\"%s\", err) } defer f.Close() if err := png.Encode(f, resImg); err != nil { fatalf(\"%s\", err) }  fmt.Printf(\"Processed %d file in %s\\n\", cnt, time.Now().Sub(start)) }  func getHtmlData(url string) ([]string, []string, error) { resp, err := http.Get(url) if err != nil { return nil, nil, err } if resp.StatusCode != http.StatusOK { return nil, nil, fmt.Errorf(\"http error: %d\", resp.StatusCode) }  buf := bytes.Buffer{} if _, err := io.Copy(&amp;buf, resp.Body); err != nil { fatalf(\"%v\", err) }  var links, images []string  for _, link := range reLink.FindAllStringSubmatch(buf.String(), -1) { links = append(links, link[1]) }  for _, img := range reImg.FindAllStringSubmatch(buf.String(), -1) { images = append(images, img[1]) }  return links, images, nil }  func getImage(url string) (image.Image, error) { resp, err := http.Get(url) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf(\"http error: %d\", resp.StatusCode) }  return png.Decode(resp.Body) }  func fatalf(format string, v ...any) { _, _ = fmt.Fprintf(os.Stderr, \"Error: %s\\n\", fmt.Sprintf(format, v...)) os.Exit(255) }<\/code><\/pre>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0434\u043e\u043b\u0433\u043e, \u043f\u043e\u0447\u0442\u0438 16 \u0441\u0435\u043a\u0443\u043d\u0434. \u0421\u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0431\u0443\u0434\u0435\u0442 \u043d\u0435 1024 \u0448\u0442., \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0441\u0441\u044b\u043b\u043e\u043a \u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0441\u043b\u043e\u043c\u0430\u044e\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0445\u043e\u0442\u044f \u0431\u044b 1 \u043f\u0440\u043e\u0431\u0435\u043b. \u041d\u0430\u0434\u043e \u043c\u0435\u043d\u044f\u0442\u044c.<\/p>\n<h2>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430<\/h2>\n<p>\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430 3 \u0431\u043b\u043e\u043a\u0430:<\/p>\n<ol>\n<li>\n<p><strong>Crawler<\/strong>: HTTP \u043a\u043b\u0438\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043c\u0435\u0435\u0442:<\/p>\n<ol>\n<li>\n<p>\u0421\u043a\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432 \u043d\u0443\u0436\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u043d\u0430 \u043d\u0438\u0445 \u0441\u0441\u044b\u043b\u043a\u0438 \u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p>\u0421\u043a\u0430\u0447\u0438\u0432\u0430\u0442\u044c PNG \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0438 \u043f\u0430\u0440\u0441\u0438\u0442\u044c \u0438\u0445.<\/p>\n<\/li>\n<\/ol>\n<\/li>\n<li>\n<p><strong>PNG Copier<\/strong>: \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432 \u043a\u043e\u043b\u043b\u0430\u0436. \u042f\u0434\u0435\u0440 CPU \u0443 \u043d\u0430\u0441 \u043c\u043d\u043e\u0433\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0438 Copier&#8217;\u043e\u0432 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u043c\u043d\u043e\u0433\u043e.<\/p>\n<\/li>\n<li>\n<p><strong>PNG Writer<\/strong>: \u0442.\u043a. \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0438\u0440\u0443\u044e\u0449\u0430\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043d\u0435 \u0432\u043b\u0435\u0437\u0430\u0435\u0442 \u0432 RAM, \u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0443\u043c\u0435\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0432\u044b\u0445\u043e\u0434\u043d\u043e\u0439 \u0444\u0430\u0439\u043b \u0447\u0430\u043d\u043a\u0430\u043c\u0438.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u0441\u0451 \u044d\u0442\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e. \u041e\u0431\u0449\u0435\u043d\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u0447\u0430\u0441\u0442\u044f\u043c\u0438 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u043d\u0430\u043b\u044b. \u041d\u0438\u0436\u0435 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0442\u043e\u043a\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<figure class=\"bordered full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043a\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u043a\u0430\u0436\u0434\u0430\u044f \u0438\u0437 \u0447\u0430\u0441\u0442\u0435\u0439 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e.<\/p>\n<h2>Crawler<\/h2>\n<p>\u0415\u0433\u043e \u0437\u0430\u0434\u0430\u0447\u0430 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u043e\u0431\u043e\u0439\u0442\u0438 \u0432\u0435\u0441\u044c \u0441\u0430\u0439\u0442 \u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u0432 \u043a\u0430\u043d\u0430\u043b \u0432\u0441\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435. \u041a\u0430\u043d\u0430\u043b \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u0431\u0443\u0444\u0435\u0440\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u043d\u0430 1024 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 (\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0432 \u0442\u0435\u0441\u0442\u0441\u0435\u0442\u0435).<\/p>\n<p>\u041f\u043e\u0440\u044f\u0434\u043e\u043a \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043a\u0430\u043a \u043f\u0440\u0438 \u043f\u043e\u0438\u0441\u043a\u0435 \u0432 \u0448\u0438\u0440\u0438\u043d\u0443, \u0437\u043d\u0430\u0447\u0438\u0442 \u0432\u0441\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0430 \u043d\u0435 \u0432 \u0441\u0442\u0435\u043a. <\/p>\n<p>\u041f\u043e\u043a\u0430 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043d\u0435 \u043f\u0443\u0441\u0442\u0430, \u0432\u044b\u043d\u0438\u043c\u0430\u0435\u043c \u0438\u0437 \u0433\u043e\u043b\u043e\u0432\u044b URL \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0438 \u043f\u0430\u0440\u0441\u0438\u043c HTML, \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u0445\u0432\u043e\u0441\u0442 \u0441\u043f\u0438\u0441\u043a\u0430, URL \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432 \u043a\u0430\u043d\u0430\u043b \u0434\u043b\u044f Copier&#8217;\u043e\u0432. \u0412 \u0441\u043e\u043a\u0440\u0430\u0449\u0451\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"go\">visited := map[string]struct{}{} queue := list.New() queue.PushBack(u.Path)  buf := bytes.Buffer{}  for queue.Len() > 0 { location := queue.Front().Value.(string) queue.Remove(queue.Front())  if _, exists := visited[location]; exists { continue } visited[location] = struct{}{}  resp, err := c.httpClient.Get(u.Scheme + \":\/\/\" + u.Host + location) \/\/ ... \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a, \u0447\u0442\u0435\u043d\u0438\u0435 HTML \u0432 buf        links, images, err := parseHTML(buf.String()) if err != nil { return err }  for _, link := range links { queue.PushBack(link) }  for _, img := range images { imgCh &lt;- ImgTask{c.id, u.Scheme + \":\/\/\" + u.Host + img} } }<\/code><\/pre>\n<p>\u0412\u043e\u043e\u0431\u0449\u0435 \u043f\u043e\u043b\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0443\u043c\u0435\u0435\u0442 \u043d\u0435 \u0445\u043e\u0434\u0438\u0442\u044c \u0437\u0430 HTML \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u0441\u0430\u0439\u0442\u044b \u0438 \u043f\u043e\u043d\u0438\u043c\u0430\u0435\u0442 \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u044b\u0435 \u0438 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438. \u0421 \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c\u044e 99,9% \u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u043c \u043d\u0430\u0431\u043e\u0440\u0435 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432 \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0431\u044b\u043b\u043e, \u043d\u043e \u043d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b.<\/p>\n<p>\u041e\u0434\u043d\u0430 \u0438\u0437 \u0432\u0430\u0436\u043d\u0435\u0439\u0448\u0438\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0437\u0434\u0435\u0441\u044c \u044d\u0442\u043e <code>parseHTML()<\/code>. \u041e\u0434\u043d\u0430 \u0438\u0437 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432 \u0431\u044b\u043b\u0430 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c RegExp&#8217;\u044b, \u0430 \u043c\u043e\u0436\u043d\u043e \u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0438\u043b\u0438 \u043d\u0435\u0442, \u044f, \u0447\u0435\u0441\u0442\u043d\u043e \u0433\u043e\u0432\u043e\u0440\u044f, \u043d\u0435 \u0437\u043d\u0430\u044e \u0434\u043e \u0441\u0438\u0445 \u043f\u043e\u0440. \u0412 \u043b\u044e\u0431\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0441\u0442\u0440\u043e\u0438\u0442\u044c DOM \u043d\u0430\u043c \u043d\u0435 \u043d\u0430\u0434\u043e, \u0430 \u043d\u0443\u0436\u043d\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u043d\u0430\u0439\u0442\u0438 \u043d\u0443\u0436\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e, \u0434\u0430 \u0438 \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434\u044b \u044f \u043b\u044e\u0431\u043b\u044e. \u041e\u043f\u044b\u0442 \u0443\u0447\u0430\u0441\u0442\u0438\u044f \u0432 HighLoad Cup&#8217;\u0430\u0445 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u0441 \u0438\u0445 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u043e\u0436\u043d\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u0440\u0435\u0448\u0438\u0442\u044c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443, \u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043d\u0443\u0436\u043d\u0430 \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u041c\u043e\u0439 \u043f\u0430\u0440\u0441\u0435\u0440 \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 \u0447\u0443\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 100 \u0441\u0442\u0440\u043e\u043a, \u043d\u043e \u0441\u043e \u0441\u0432\u043e\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0435\u0439 \u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u043f\u043b\u044e\u0441 \u0443\u043c\u0435\u0435\u0442 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0438 \u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445:<\/p>\n<pre><code class=\"go\">func parseHTML(s string) ([]string, []string, error) { images := make([]string, 0, 1) links := make([]string, 0, 2)  for len(s) > 0 { \/\/ Find HTML tag start pos := strings.IndexByte(s, '&lt;') if pos == -1 { break } s = s[pos+1:]  s = skipSpaces(s)  \/\/ Find end of token pos = strings.IndexAny(s, \" \\t\\n\\r>\") if pos == -1 { return nil, nil, errors.New(\"invalid html\") }  token := s[:pos] s = s[pos:]  \/\/ Skip comments if token == \"!--\" { pos := strings.Index(s, \"-->\") if pos == -1 { break } s = s[pos+3:] continue }  \/\/ Find end of HTML tag pos = strings.IndexByte(s, '>') if pos == -1 { return nil, nil, errors.New(\"invalid html\") } attrs := s[:pos] s = s[pos+1:]  if attrs != \"\" { switch toLower(token) { case \"a\": pos := strings.Index(attrs, \"href\") if pos != -1 { attrs = skipSpaces(attrs[pos+4:]) if attrs[0] == '=' { attrs = skipSpaces(attrs[1:]) } if attrs[0] != '\"' { return nil, nil, errors.New(\"invalid html\") }  pos = strings.IndexByte(attrs[1:], '\"') if pos == -1 { return nil, nil, errors.New(\"invalid html\") } if pos > 0 { links = append(links, attrs[1:pos+1]) } } case \"img\": pos := strings.Index(attrs, \"src\") if pos != -1 { attrs = skipSpaces(attrs[pos+3:]) if attrs[0] == '=' { attrs = skipSpaces(attrs[1:]) } if attrs[0] != '\"' { return nil, nil, errors.New(\"invalid html\") }  pos = strings.IndexByte(attrs[1:], '\"') if pos == -1 { return nil, nil, errors.New(\"invalid html\") } if pos > 0 { images = append(images, attrs[1:pos+1]) } } } } }  return links, images, nil }  func skipSpaces(s string) string { for len(s) > 0 { switch s[0] { case ' ', '\\t', '\\r', '\\n': s = s[1:] default: return s } }  return s }  func toLower(s string) string { b := []byte(s) for i, c := range b { if c >= 'A' &amp;&amp; c &lt;= 'Z' { b[i] = c - 'A' + 'a' } }  return string(b) }<\/code><\/pre>\n<p><code>toLower()<\/code> \u0437\u0430\u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0432\u043e\u0439, \u0442.\u043a. \u0432 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u043c \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a, \u0430 \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043b\u0438\u0448\u043d\u0438\u0445 \u043d\u0430\u043d\u043e\u0441\u0435\u043a\u0443\u043d\u0434 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043f\u043e\u043c\u0435\u0448\u0430\u0435\u0442. \u041d\u0438\u0436\u0435 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435:<\/p>\n<pre><code>BenchmarkStdToLower-16    30112750        40.81 ns\/op       8 B\/op       1 allocs\/op BenchmarkToLower-16       55108370        33.11 ns\/op       8 B\/op       1 allocs\/op<\/code><\/pre>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435 \u043f\u0430\u0440\u0441\u0435\u0440 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0441\u044f \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u044b\u043c \u0438 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0431\u0435\u0437 \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043f\u0430\u043c\u044f\u0442\u0438 \u0432 \u043a\u0443\u0447\u0435, \u0435\u0441\u043b\u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u0442\u044c \u0441 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u043c\u0438 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043c\u0438, \u0442\u043e<\/p>\n<pre><code>BenchmarkRegExp-16          440782      4176 ns\/op     723 B\/op      10 allocs\/op BenchmarkParser-16         1986980      595.2 <\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-345412","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/345412","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=345412"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/345412\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=345412"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=345412"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=345412"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}