Quantcast
Channel: Найцікавіше на DOU
Viewing all articles
Browse latest Browse all 8115

Визначаємо вартість декоратора в Golang

$
0
0

Привіт, мене звати Ярослав, займаюсь розробкою сервісу для збереження активів у криптовалюті в компанії ITAdviser, розробляємо на Go. У цій статті розглянемо декоратор, його вартість і чи варто використовувати його в розробці нових сервісів.

Коротко про мене

Кілька років тому почав цікавитись Go, подарував другу на день народження книжку «The Go Programming Language», сам грався задачами з LeetCode, облишив, через півроку продовжив, вийшов професійний курс від «Техносфери», передивився і цього було достатньо, щоб почати працювати як Junior Go.

Go зацікавив тестами та бенчмарками з коробки, можливістю розбиратись в коді стандартних бібліотек, які теж написані на Go. А ще в Києві хороше Go ком’юніті. В деяких мовах рішення певних задач лаконічніше та красивіше, ніж в інших. Уже вкотре зустрічаю теми, де автори описують, як бачать ідеальну мову програмування, а інші ж створюють такі мови, прикладу Ruby.

Що таке декоратор

Так, в Go зручно реалізувати патерн декоратор. Це відомий патерн, вже описаний в книжці Gang of Four «Design Patterns: Elements of Reusable Object-Oriented Software» (та початківцям краще починати з «Head First Design Patterns»).

Декоратор зручний, коли треба розширити функціональність без змін компонентів. Мені він нагадує матрьошку, якій треба розмалювати іншим кольором руки. Беремо матрьошку, обертаємо її в прозору плівку, розмальовуємо руки, плівка та малюнок і будуть декоратором. Шрек приводиву приклад цибулю.

В основному проекті ми використовуємо декорацію для запису в журнал взаємодії через API клієнти та для синхронізації.

Дуже просто покрити тестами основну логіку, а всі додаткові обгортки винести в декорацію. Але перед тим як так структурувати частину проекта через декоратори, треба довести, що його вартість мала.

Реалізація

В Go реалізувати декоратор простіше, ніж через ООП. Візьмемо штучний приклад класу на PHP з двома методами. Один треба змінити, а інший залишити, як є:

interface GeneratorInterface
{
    public function increment(int $step): int;

    public function stats(): Stats;
}

class GeneratorIncrementDecorator implements GeneratorInterface
{
    private $source;

    private $coefficient;

    public function __construct(GeneratorInterface $source, int $coefficient)
    {
        $this->source = $source;
        $this->coefficient = $coefficient;
    }

    public function increment(int $step): int
    {
        // decorated
        return $this->increment($step * $this->coefficient);
    }

    public function stats(): Stats
    {
        // as is
        return $this->stats();
    }
}

class Stats{}

А тепер на Go:

type Generator interface {
	Increment(step int) int
	Stats() Stats
}

type GeneratorIncrementDecorator struct {
	Generator
	coefficient int
}

func NewGeneratorIncrementDecorator(source Generator, coefficient int) Generator {
	return GeneratorIncrementDecorator{
		Generator:   source,
		coefficient: coefficient,
	}
}

func (d GeneratorIncrementDecorator) Increment(step int) int {
	return d.Generator.Increment(step * d.coefficient)
}

type Stats struct{}

В Go декоруємо тільки потрібний метод, а метод Stats вбудовується. В офіційній документацій це називається Embedding. В PHP, як і в Java та C#, треба буде обгортати усі методи.

А тепер приклад, щоб визначити вартість. Візьмемо структуру з однаковими функціями.

type (
        source interface {
            increment(int) int
            wrap(int) int
            proxy(int) int
            same(int) int
        }

        handler struct {
        }
    )

    func (handler) increment(s int) int {
        return s + 1
    }

    func (handler) wrap(s int) int {
        return s + 1
    }

    func (handler) proxy(s int) int {
        return s + 1
    }

    func (handler) same(s int) int {
        return s + 1
    }

Продекоруємо її різними методами:

type (
        decorator struct {
            source
        }
    )

    func newDecorator(source source) source {
        return decorator{source}
    }

    func (d decorator) increment(s int) int {
        return d.source.increment(s) + 1
    }

    func (d decorator) wrap(s int) int {
        return d.source.wrap(s + 1)
    }

    func (d decorator) proxy(s int) int {
        return d.source.proxy(s)
    }

    // embedding
    //func (d decorator) same(s int) int {
    //	return d.source.same(s)
    //}

Додамо benchmark на кожну функцію інтерфейсу та допоміжну тестову функцію, щоб декорувати N разів:

    import "testing"

    const N = 127

    func BenchmarkSource(b *testing.B) {
        handler := handler{}

        for i := 0; i < b.N; i++ {
            handler.increment(i)
        }
    }

    func BenchmarkDecoratorIncrement(b *testing.B) {
        handler := createNTimesDecoratedHandler(handler{}, N)

        for i := 0; i < b.N; i++ {
            handler.increment(i)
        }
    }

    func BenchmarkDecoratorWrap(b *testing.B) {
        handler := createNTimesDecoratedHandler(handler{}, N)

        for i := 0; i < b.N; i++ {
            handler.wrap(i)
        }
    }

    func BenchmarkDecoratorProxy(b *testing.B) {
        handler := createNTimesDecoratedHandler(handler{}, N)

        for i := 0; i < b.N; i++ {
            handler.proxy(i)
        }
    }

    func BenchmarkDecoratorSame(b *testing.B) {
        handler := createNTimesDecoratedHandler(handler{}, N)

        for i := 0; i < b.N; i++ {
            handler.same(i)
        }
    }

    func createNTimesDecoratedHandler(source source, times int) source {
        result := source

        for i := 0; i < times; i++ {
            result = newDecorator(result)
        }

        return result
    }

І запустимо:

go test ./... -bench=. -benchmem

Результати (середовище: go version go1.11.1 linux/amd64):

Для N = 0:

Назва тестуКількість ітераційСередній час ітераціїВиділення пам’яті
Source20000000000.38 ns/op0 B/op 0 allocs/op
Increment3000000004.72 ns/op0 B/op 0 allocs/op
Wrap3000000004.99 ns/op0 B/op 0 allocs/op
Proxy3000000004.97 ns/op0 B/op 0 allocs/op
Same3000000004.78 ns/op0 B/op 0 allocs/op

Для N = 127:

Назва тестуКількість ітераційСередній час ітераціїВиділення пам’яті
Increment10000001299 ns/op0 B/op 0 allocs/op
Wrap10000001257 ns/op0 B/op 0 allocs/op
Proxy10000001245 ns/op0 B/op 0 allocs/op
Same2000000725 ns/op0 B/op 0 allocs/op

Висновки

Операція додавання дуже швидка ~ 0.4 наносекунди, а от обгортка інтерфейсу ~ 4.5 наносекунди. Декорація має свою вартість ~ 10 наносекунд, навіть через embedding ~ 5-6 наносекунд.

Якщо зробити загальний висновок — після впровадження декорації стало простіше розробляти нові сервіси.


Viewing all articles
Browse latest Browse all 8115

Trending Articles