В этой статье я сравню производительность среди фиксированного типа, интерфейс в качестве параметра принимает любое значение и перейду к дженерикам. Вы знаете, что Go Generics — самый медленный.
Основы
Сначала рассмотрим концепции go generics, интерфейс в качестве параметра принимает любое значение и фиксированный тип.
Фиксированный тип
В программировании на Go «фиксированный тип» обычно относится к конкретному типу с определенным известным типом во время компиляции. Это отличается от использования интерфейсов или универсальных шаблонов, где точный тип переменной или параметра функции может быть более гибким и определяется во время выполнения или при создании экземпляра универсальной функции или типа.
Фиксированные типы включают встроенные типы, такие как int
, float64
, string
и bool
, а также настраиваемые типы, такие как структуры, массивы и срезы. При использовании фиксированного типа вы явно определяете структуру данных и ее свойства, что обеспечивает строгую безопасность типов и приводит к более эффективному выполнению кода. Ниже приведен пример.
type Rectangle struct { width float64 height float64 } func Area(rect Rectangle) float64 { return rect.width * rect.height }
В приведенном выше коде я определяю структуру Rectangle
как настраиваемый фиксированный тип со свойствами width
и height
. Функция Area
принимает в качестве параметра Rectangle
, а тип фиксируется и известен во время компиляции. Это обеспечивает безопасность типов и эффективное выполнение, поскольку компилятор Go может оптимизировать код для определенного типа. Использование фиксированных типов может обеспечить надежную безопасность типов, лучшую производительность и более четкий код, но также может привести к дублированию кода и снижению гибкости.
Интерфейс как параметр принимает любое значение
В Golang интерфейс — это набор сигнатур методов, которые может реализовать тип.Вы можете использовать интерфейс как тип для создания переменной или параметра функции, который принимает любое значение, тип которого реализует указанные методы интерфейса. strong> Это позволяет вам писать более гибкий и пригодный для повторного использования код, определяя поведение, а не конкретные типы.
Ниже приведен простой пример.
package main import "fmt" func PrintValue(value interface{}) { fmt.Printf("Value: %v, Type: %T\n", value, value) } func main() { intValue := 42 stringValue := "hello" floatValue := 3.14 PrintValue(intValue) // Value: 42, Type: int PrintValue(stringValue) // Value: hello, Type: string PrintValue(floatValue) // Value: 3.14, Type: float64 }
В приведенном выше коде PrintValue принимает в качестве параметра пустой интерфейс и печатает значение. Он может принимать любое значение. Используя интерфейс в качестве типа, мы можем создавать функции и переменные, которые работают с любым значением, которое реализует требуемые методы, способствуя повторному использованию и гибкости кода.
До дженериков вы часто использовали интерфейсы или утверждения типов для создания повторно используемого кода. Однако это могло привести к ошибкам во время выполнения и коду, который был бы менее эффективным из-за накладных расходов на использование интерфейсов. С помощью универсальных шаблонов вы можете создавать код, который работает с несколькими типами, но при этом проверяется на правильность во время компиляции, что помогает избежать ошибок во время выполнения и повышает производительность.
Перейти к дженерикам
Универсальные шаблоны Go, представленные в Go 1.18, — это языковая функция, которая позволяет вам писать функции или типы, которые работают с несколькими типами, не требуя явных преобразований типов или дублирования кода. Обобщения позволяют создавать более многоразовый и гибкий код, сохраняя при этом высокий уровень безопасности, которым славится Go.
Обобщения в Go основаны на концепции параметров типа. Параметр типа — это заполнитель для типа, который определяется при создании экземпляра универсальной функции или типа с фактическими типами. Вы можете определить универсальные функции или типы, используя квадратные скобки []
, за которыми следует список параметров типа.
Ниже приведен пример. Функция Identity
принимает параметр одного типа T
, который может быть любого типа. Ключевое слово any
— это ограничение типа, означающее, что параметр типа может быть любого типа.
func Identity[T any](value T) T { return value }
Обобщения предоставляют мощный способ написания более многократно используемого и эффективного кода на Golang, сохраняя при этом высокую безопасность типов и характеристики производительности, которыми известен этот язык.
Какой из них лучший спектакль?
После того, как мы узнаем все три вышеперечисленных, давайте сравним их, чтобы узнать, какой из них лучше. Следующий код является содержимым файла instance_test.go.
package opinterface import "testing" // Fixed type approach func DoSomethingFixed(value int) int { return value } // Interface approach func DoSomethingInterface(value interface{}) interface{} { return value } // Generics approach (Go 1.18+ required) func DoSomethingGeneric[T any](value T) T { return value } func BenchmarkDoSomethingGeneric(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = DoSomethingGeneric(66) } } func BenchmarkDoSomethingInterface(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = DoSomethingInterface(66) } } func BenchmarkDoSomethingFixed(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = DoSomethingFixed(66) } }
Выполните «go test -bench .», скриншот результата теста ниже.
Из приведенного выше результата это очень ясно. Генерализатор Go самый низкий, он в 3 раза ниже интерфейса и фиксированного типа. Скорость интерфейса очень похожа на скорость фиксированного типа.
Сценарии с использованием интерфейса и Go Generics
Сценарии, в которых используется фиксированный тип, легко понять. Ключевым моментом является то, какие сценарии используют интерфейс и Go Generics.
И дженерики, и интерфейсы Go играют свою роль в написании гибкого и многократно используемого кода. Вот несколько сценариев, в которых дженерики могут быть предпочтительнее интерфейсов:
- Безопасность типов: Обобщения обеспечивают безопасность типов во время компиляции. Это означает, что если вы пишете код, в котором хотите гарантировать, что могут использоваться только определенные типы данных, дженерики будут лучшим выбором. С интерфейсом вы потенциально можете передать любой тип, реализующий интерфейс, что может привести к ошибкам во время выполнения, если используется неправильный тип.
- Производительность: Использование интерфейсов в Go может повлиять на производительность из-за динамической диспетчеризации методов и потенциальной упаковки/распаковки значений. Это особенно верно в отношении критичного к производительности кода или в ситуациях, когда вы имеете дело с примитивными типы (например,
int
илиfloat64
).С помощью универсальных шаблонов вы можете писать код, который работает для многих типов, но компилируется в конкретный эффективный код для каждого типа. - Избегайте утверждений типа или переключателей типов. Если ваш код, использующий интерфейсы, требует утверждений типов или переключателей типов для обработки разных конкретных типов по-разному, это может быть признаком того, что дженерики подходят лучше. С помощью универсальных шаблонов вы можете писать функции или типы, которые работают с несколькими типами, не требуя явного преобразования типов.
- Работа со срезами, картами, каналами любого типа: До дженериков, если вы хотели создать функцию, которая работает со срезами, картами или каналами любого типа, вам приходилось использовать интерфейсы, а затем выполнять утверждения типа. С помощью универсальных шаблонов вы можете напрямую указывать их как параметры типа, что делает ваш код чище и безопаснее.
Следующие сценарии используют интерфейс в качестве параметра, который принимает любое значение лучше, чем go generics.
- Работа с небольшим количеством типов: если вам нужно создать функцию, которая принимает небольшое количество типов, и вы не хотите создавать общую функцию для каждого типа, используя интерфейс в качестве параметра. может быть более практичным.
- Прием действительно разнородных данных. Если вы хотите создать функцию, которая может принимать данные любого типа, независимо от типа, подходящим выбором будет использование интерфейса в качестве параметра для приема любого значения. В этом случае вы можете использовать пустой интерфейс (
interface{}
) в качестве типа параметра. - Поведение во время выполнения. Когда вам нужно принимать решения на основе информации о типе параметров во время выполнения, может оказаться полезным использование интерфейсов в качестве параметров. С интерфейсами вы можете использовать утверждения типа или переключатели типов для проверки и обработки конкретного типа параметра во время выполнения.
- Обратная совместимость: если вы пишете библиотеку или модуль, который должен быть совместим с более ранними версиями Go (до Go 1.18), вам придется использовать интерфейсы, поскольку дженерики недоступны в эти версии.
Заключение
Таким образом, фиксированные типы обеспечивают надежную безопасность типов и лучшую производительность, но могут привести к дублированию кода и снижению гибкости. Интерфейсы как параметры обеспечивают гибкость и возможность повторного использования кода за счет определения поведения. Генераторы Go обеспечивают баланс между гибкостью и производительностью, позволяя создавать многоразовый и эффективный код с высокой безопасностью типов во время компиляции. Выбор между этими подходами зависит от вашего конкретного варианта использования и желаемого баланса между возможностью повторного использования, гибкостью и производительностью.
Как я сравнивал их выше, Go Generics — самый медленный, почти в 3 раза медленнее, чем два других, но обеспечивает гибкость и производительность. Вы не должны быть ленивым сусликом, который всегда использует go generics. На мой взгляд, если вы знаете точный тип, не используйте go generics. И используйте go generics или интерфейс, поскольку параметр принимает любое значение, это зависит от конкретного сценария.
Если вас интересуют другие статьи о производительности, просмотрите ниже.
Понимание времени компиляции и времени выполнения! Улучшение производительности Golang(1)
Структура в лучшем порядке! Улучшение производительности Golang(2)
Освойте 4 способа соединения струн! Улучшение производительности Golang(3)
Рекомендации по использованию массивов и срезов! Улучшение производительности Golang(4)
Не используйте Reflect без необходимости! Улучшение производительности Golang(5)
Злоупотребление Go Generics снижает эффективность! Улучшение производительности Golang(7)
Возможно, вас также интересуют шаблоны проектирования Golang.
Чтобы просмотреть шаблоны поведенческого проектирования в Golang, нажмите здесь.
Чтобы просмотреть шаблоны креативного дизайна в Golang, нажмите здесь.
Чтобы просмотреть шаблоны структурного проектирования в Golang, нажмите здесь.
Спасибо, что читаете. Пожалуйста, хлопайте и следуйте за мной. Я с удовольствием отвечу на все ваши вопросы, если вы спросите меня в комментарии. Нажмите на следующую ссылку, чтобы стать средним участником.
Нажмите, чтобы стать средним участником и читать неограниченное количество историй!