Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

30 банковских дней-2

Жена попросила во вчерашнюю программу ряд улучшений внести и пожаловалась, что она на одном из компьютеров на работе запускается медленно. Ну, я решил переписать её на Гоу, раз такое дело, может быстрее стартовать будет. Кроме того, мне всегда интересно посмотреть как одна и та же программа выглядит на разных языках программирования.

package main

import (
    "encoding/json"
    "os"
    s "strconv"
    t "time"
    "net/http"
    "io/ioutil"
    "fmt"
    r "regexp"
    "bufio"
    "strings"
    fp "path/filepath"
)

type date struct {
    D byte
    M byte
    Y int
}

func CreateDate(d, m string, y int) date {
    day, _   := s.Atoi(d)
    month, _ := s.Atoi(m)

    return date{D:byte(day), M:byte(month), Y:y}
}

func (d date) FindIn(list []date) bool {
    for _, cur := range list {
        if d.D == cur.D && d.M == cur.M && (d.Y == cur.Y || cur.Y == 0) {
            return true
        }
    } 

    return false
}

func (d date) ToTime() t.Time {
    return t.Date(d.Y, t.Month(d.M), int(d.D), 0, 0, 0, 0, t.UTC)
}

func (d date) IsWeekEnd() bool {
    dw := string(d.ToTime().Weekday())

    return dw == "Saturday" || dw == "Sunday"
}


func (d date) Add(delta string) date {
    dur, _ := t.ParseDuration(delta)
    t := d.ToTime().Add(dur)

    return date{D:byte(t.Day()), M:byte(t.Month()), Y:t.Year()}
}

func (d date) String() string {
    return fmt.Sprintf("%02d.%02d.%04d", d.D, d.M, d.Y)
}

func loadCache(name string) (holidays []date) {
    f, err := os.OpenFile(name, os.O_RDONLY, 0666)

    if err != nil {
        return nil
    }

    defer f.Close()

    if err := json.NewDecoder(f).Decode(&holidays); err != nil {
        return nil
    }

    return
}

func saveCache(name string, holidays []date) bool {
    f, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)

    if err != nil {
        return false
    }

    defer f.Close()

    if err := json.NewEncoder(f).Encode(&holidays); err != nil {
        return false
    }

    return true
}

func getCurrentCalend() []byte {
    if resp, err := http.Get("http://www.calend.ru/work/"); err == nil {
        defer resp.Body.Close()

        if body, err := ioutil.ReadAll(resp.Body); err == nil {
            return body
        }
    }

    return nil
}

func getCurrentHolidays() (dates []date) {
    table, _ := r.Compile(`(?s)<table\s.+?time_of_death(.+)</table>`)
    chunk, _ := r.Compile(`(?s)<td\s+class="\S+\s+col5"\s+day="(\d+)"\s+month="(\d+)"`)

    dates = []date{}

    year := t.Now().Year()

    for _, value := range chunk.FindAllStringSubmatch(string(table.Find(getCurrentCalend())), -1) {
        dates = append(dates, CreateDate(value[1], value[2], year))
    }

    return
}

func ReadUserInput(message string) string {
    fmt.Print(message)

    input, _ := bufio.NewReader(os.Stdin).ReadString('\n')
    return input
}

func main() {
    var currentholidays []date

    year := t.Now().Year()
    cachename := s.Itoa(year) + ".json"

    // если кеш существует
    if _, e := os.Stat(cachename); e == nil {
        currentholidays = loadCache(cachename)
    } else {
        currentholidays = getCurrentHolidays()
        saveCache(cachename, currentholidays)
    }

    // загружаем другие кеши
    caches, _ := fp.Glob("*.json")
    for _, name := range caches {
        if name != cachename {
            currentholidays = append(currentholidays, loadCache(name)...)
        }
    }

    // опрашиваем пользователя
    mask, _  := r.Compile(`^(?m)(\d+)\D+(\d+)`)

    for {
        var userdate date

        for {
            d := mask.FindStringSubmatch(ReadUserInput("Enter date (dd.mm): "))
            if len(d) > 0 {
                userdate = CreateDate(d[1], d[2], year)
                break
            }
        }

        var period int

        for {
            period, _ = s.Atoi(strings.TrimRight(ReadUserInput("Enter period: "), "\r\n"))

            if period > 0 {
                break
            }
        }

        for ; period > 0; period-- {
            for {
                userdate = userdate.Add("24h")

                if !userdate.FindIn(currentholidays) && !userdate.IsWeekEnd() {
                    break
                }
            }
        }


        fmt.Printf("Result: %s\n\n", userdate)
    }
}

Меня поразил размер скомпилированной программы. Вот эта мелочь под «Маком» занимает 3,9Мб, под «Виндой» — 3,7! Жена попросила убрать гуишное окошко, поэтому я сделал программу полностью консольной. Два разочарования: в регулярных выражениях под Виндой неверно определяется конец строки (не учитывается «\r», судя по всему) и второе — в Гоу нет ничего, чтобы работать с однобайтными кодировками, а в консоли Винды до сих пор CP866. Пришлось выводить всё по-английски, писать конвертирующую функцию мне лень.

Объём увеличился, во-первых, из-за улучшений, во-вторых, мне хотелось хранить файлы кешей в каком-нибудь более-менее читаемом формате, чтобы вручную добавить в кеш татарстанские праздники, так что часть объёма (класс «date и методы работы с ним) как раз для этого.

13 комментариев
rodem 2013

Меня поразил размер скомпилированной программы

Так вроде всегда так было. На Stack Overflow постоянно встречается why is the Go compiled version is relatively huge?

statically linking the Go runtime is the default. That page also tells you how to set up for dynamic linking
http://golang.org/doc/install/gccgo

А сколько такой будет весить на маке?

Евгений Степанищев (bolknote.ru) 2013

Комментарий для rodem:

Так вроде всегда так было. На Stack Overflow постоянно встречается why is the Go compiled version is relatively huge?

Меня печалит, что с каждой версией всё больше. Моя программа makecorner ( https://github.com/bolknote/MakeCorner ) занимала, скомпилированная версией r60.3 1,6Мб, версия 1.0 компилирует уже 2,2!

А сколько такой будет весить на маке?

Какой такой? Не понял.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для rodem:

Upx — наше всё. Тем более, я добился-таки от автора, чтобы он починил его и теперь бинарники, скомпилированные Гоу, им тоже сжимаются.

rodem 2013

Комментарий для Евгения Степанищева:

The Go creators explicitly prefer dynamic linking: harmful.cat-v.org/software/dynamic-linking. Google statically links all its C and C++: http://www.reddit.com/r/golang/comments/tqudb/are_go_binaries_really_entirely_statically_linked/c4p1awi

Fun fact: Google statically links all their C/C++ code they run on their servers.
It seems you are severely confused regarding the characteristics of dynamic vs. static linking. Static linking is faster and more efficient.
If you have many processes static linking also means fork() is much faster.

Вот собственно и объяснение. В каком-то видео давнишнем, Роб Пайк говорил, что размер это не главное, главное это скорость.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для rodem:

Ну, там точно не всё линкуется статически. Вот мой makecorner, который я уже упоминал:

$ otool -L corner
corner:
/usr/local/lib/libgd.2.0.0.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 0.0.0, current version 0.0.0)

Евгений Степанищев (bolknote.ru) 2013

Комментарий для rodem:

А программа, которая в этом после, слинковалась с неожиданными для меня библиотеками:

otool -L calend
calend:
/usr/lib/libSystem.B.dylib (compatibility version 0.0.0, current version 0.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 0.0.0, current version 0.0.0)
/System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 0.0.0, current version 0.0.0)

Vlad528 2013

Жена попросила убрать гуишное окошко, поэтому я сделал программу полностью консольной.

аплодирую стоя вашей жене

zg (zg.livejournal.com) 2013

chcp 65001

Евгений Степанищев (bolknote.ru) 2013

Комментарий для zg.livejournal.com:

Знать бы как это сделать из программы на «Гоу» :)

Никита Баксаляр (n-baksalyar.ya.ru) 2013

Комментарий для Евгения Степанищева:

Вот эта мелочь под «Маком» занимает 3,9Мб, под «Виндой» — 3,7!

Хм, а сколько заняла программа, скомпилированная PyInstaller’ом?
Вроде бы у скомпилированных программ на Питоне примерно такой же объем?

Евгений Степанищев (bolknote.ru) 2013

Комментарий для n-baksalyar.ya.ru:

Больше заняла, но там интерпретатор Пайтона целиком лежит.

Доброжелатель 2013

Товарищи, хватит жаловаться! Да, ваша программа уже не поместится на дискетку. Но у вас отличный язык программирования, отличная стандартная библиотека и очень неплохой garbage collector, да ещё и goroutines! И всё это компилируется моментально, запускается молниеносно, работает прекрасно. Я каждый раз удивляюсь, написав что-нибудь на Go, что это работает, что это так просто и без танцев с бубном. Жаловаться на размер программ уже просто смешно, т. к. это вас отвлекает от всего прекрасного, что вам даёт Golang.

Если вас напрягает то, что вам приходится электронной почтой отправлять такие большие файлы, то вот вам решение: отправляйте ссылку на установщик Golang и исходник программы (он ведь вообще крохотный), с батником «go run src.go». Заодно жена сможет что-нибудь подправить :)

Евгений Степанищев (bolknote.ru) 2013

Комментарий для Доброжелатель:

Ну почему же хватит? Должны же быть какие-то разумные пределы?

Заодно жена сможет что-нибудь подправить :)

Она не умеет программировать.