Как отдавать сообщения различных типов?

Ссылка скопирована
Go
1 ответ
type Packet struct { Type uint8 Timestamp uint32 Data []byte } func ReadPacket(packet *Packet) error { // somehow read data into the packet return nil } type Message interface {} type VideoMessage struct { Timestamp uint32 Data []byte } type CallMessage struct { CallID uint32 Procedure string } func ReadMessage() (Message, error) { var packet Packet if err := ReadPacket(&packet); err != nil { return err } switch packet.Type { case PacketVideo: return &VideoMessage{ Timestamp: packet.Timestamp, Data: packet.Data, }, nil case PacketUserData: callID, procedure := getCallInfo(&packet) return &UserDataMessage{ CallID: callID, Procedure: procedure, }, nil default: return nil, ErrInvalidPacketType } }

Сейчас ситуация такая: проблема в том, что я постоянно выделяю память для сообщений.
По вводным: есть идея реализовать union тип по примеру C, но тогда нарушится безопасность типов.
Сейчас ситуация такая: либо можно попробовать использовать какой-то пул памяти и выделять место под сообщения там, но я не уверен насчет этого решения
Либо что-то такое

func ReadCallMessage(packet *Packet, mesg *CallMessage) { // read packet into mesg } func main() { for { var packet Packet if err := ReadPacket(&packet); err != nil { continue } switch packet.Type { case PacketCall: mesg := CallMessage{} ReadCallMessage(&packet, &mesg) } } }

Какие варианты решения стоит рассмотреть?

Нужно решить такую задачу?

Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.

Заказать помощь
Лучший ответ
1
Ирина WP Ответ

В Go обычно не нужно пытаться сделать C-style union ради экономии нескольких аллокаций. Такой подход ухудшит читаемость и безопасность типов. Если у вас реально горячий участок и много пакетов, сначала измерьте профилем, что именно аллокации сообщений являются проблемой. Очень часто основная стоимость не в структуре VideoMessage, а в буфере Data []byte, парсинге и I/O.

Базовый идиоматичный вариант через interface нормален:

type Message interface {
    Kind() uint8
}
 
func (m VideoMessage) Kind() uint8 { return PacketVideo }
func (m CallMessage) Kind() uint8  { return PacketUserData }

type Message interface { Kind() uint8 } func (m VideoMessage) Kind() uint8 { return PacketVideo } func (m CallMessage) Kind() uint8 { return PacketUserData }

Но если хотите уменьшить аллокации, можно возвращать значение-обёртку с union-like полями и явным типом:

type Message struct {
    Type  uint8
    Video VideoMessage
    Call  CallMessage
}
 
func ReadMessage() (Message, error) {
    var packet Packet
    if err := ReadPacket(&packet); err != nil {
        return Message{}, err
    }
 
    switch packet.Type {
    case PacketVideo:
        return Message{
            Type: PacketVideo,
            Video: VideoMessage{
                Timestamp: packet.Timestamp,
                Data: packet.Data,
            },
        }, nil
    case PacketUserData:
        callID, procedure := getCallInfo(&packet)
        return Message{
            Type: PacketUserData,
            Call: CallMessage{CallID: callID, Procedure: procedure},
        }, nil
    default:
        return Message{}, ErrInvalidPacketType
    }
}

type Message struct { Type uint8 Video VideoMessage Call CallMessage } func ReadMessage() (Message, error) { var packet Packet if err := ReadPacket(&packet); err != nil { return Message{}, err } switch packet.Type { case PacketVideo: return Message{ Type: PacketVideo, Video: VideoMessage{ Timestamp: packet.Timestamp, Data: packet.Data, }, }, nil case PacketUserData: callID, procedure := getCallInfo(&packet) return Message{ Type: PacketUserData, Call: CallMessage{CallID: callID, Procedure: procedure}, }, nil default: return Message{}, ErrInvalidPacketType } }

Да, это менее красиво, чем interface, зато без выделения под конкретную структуру при возврате указателя. Использование:

msg, err := ReadMessage()
if err != nil {
    return err
}
 
switch msg.Type {
case PacketVideo:
    handleVideo(msg.Video)
case PacketUserData:
    handleCall(msg.Call)
}

msg, err := ReadMessage() if err != nil { return err } switch msg.Type { case PacketVideo: handleVideo(msg.Video) case PacketUserData: handleCall(msg.Call) }

Ещё вариант — передавать обработчик callback-ом, тогда можно вообще не создавать отдельный message object:

func ReadMessage(handle func(Message) error) error {
    msg, err := readMessageValue()
    if err != nil {
        return err
    }
    return handle(msg)
}

func ReadMessage(handle func(Message) error) error { msg, err := readMessageValue() if err != nil { return err } return handle(msg) }

Итог: сначала сделайте benchmark и pprof. Если аллокации действительно мешают, используйте value-структуру с полем Type и вложенными вариантами. Это безопаснее и проще, чем имитировать union через unsafe.

Другие ответы (0)

Пока нет других ответов. Будьте первым, кто поможет автору.

Ответить на вопрос

комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Вам также может быть интересно