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

Привіт, мене звати Ярослав, займаюсь розробкою сервісу для збереження активів у криптовалюті в компанії 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(int s) int {
 return s + 1
}

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

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

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

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

type (
 decorator struct {
source
}
)

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

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

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

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

 // embedding
 //func (d decorator) same(int s) 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:

Назва тесту Кількість ітерацій Середній годину ітерації Виділення пам'яті
Source 2000000000 0.38 ns/op 0 B/op 0 allocs/op
Increment 300000000 4.72 ns/op 0 B/op 0 allocs/op
Wrap 300000000 4.99 ns/op 0 B/op 0 allocs/op
Proxy 300000000 4.97 ns/op 0 B/op 0 allocs/op
Same 300000000 4.78 ns/op 0 B/op 0 allocs/op

Для N = 127:

Назва тесту Кількість ітерацій Середній годину ітерації Виділення пам'яті
Increment 1000000 1299 ns/op 0 B/op 0 allocs/op
Wrap 1000000 1257 ns/op 0 B/op 0 allocs/op
Proxy 1000000 1245 ns/op 0 B/op 0 allocs/op
Same 2000000 725 ns/op 0 B/op 0 allocs/op

Висновки

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

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

Опубліковано: 01/02/19 @ 11:42
Розділ Різне

Рекомендуємо:

Финстрип за Січень 2019, інфо-сайти. Місяць приніс відкат майже в 2 рази
React Hooks — огляд можливостей нового API
Розробка реактивних та розподілених систем з Vert.x
Токсичний HR: дії, які отруюють команду
Три основні проблеми розумних будинків і як їх можна вирішити