gpt4 book ai didi

使用调度程序的 C++ NTOH 转换 - 事件队列

转载 作者:行者123 更新时间:2023-11-28 05:11:55 25 4
gpt4 key购买 nike

我们正在将 C 语言的遗留代码重写为 C++。在我们系统的核心,我们有一个连接到 master 的 TCP 客户端。主人将不断地流式传输消息。每个套接字读取都会产生 N 条格式为 {type, size, data[0]} 的消息。

现在我们不将这些消息复制到单独的缓冲区中 - 而是将消息开头的指针、长度和指向底层缓冲区的 shared_ptr 传递给工作人员。

旧的 C 版本是单线程的,会像下面这样进行就地 NTOH 转换:

struct Message {
uint32_t something1;
uint16_t something2;
};

process (char *message)
Message *m = (message);
m->something1 = htonl(m->something1);
m->something2 = htons(m->something2);

然后使用消息。

在登录新代码时会遇到一些问题。

  1. 由于我们将消息分派(dispatch)给不同的工作人员,每个执行 ntoh 转换的工作人员都会导致缓存未命中问题,因为消息未在缓存中对齐 - 即没有填充 b/w 消息。

  2. 相同的消息可以由不同的工作人员处理 - 这种情况下消息需要在本地处理并中继到另一个进程。这里中继 worker 需要原始网络顺序的消息,本地工作需要转换为主机顺序。显然,由于消息不重复,两者都不能满足。

我想到的解决方案是 -

  1. 复制消息并为所有中继工作人员发送一份拷贝(如果有)。在调度程序本身中对属于同一缓冲区的所有消息进行 ntoh 转换,然后再进行调度 - 例如通过调用 handler->ntoh(message); 来解决缓存未命中问题。

  2. 向每位 worker 发送原件。每个worker将消息复制到本地缓冲区,然后进行ntoh转换并使用它。在这里,每个工作人员都可以使用特定于线程的 (thread_local) 静态缓冲区作为暂存器来复制消息。

现在我的问题是

  1. 选项 1 是进行 ntoh 转换的方法 - C++sy 吗?我的意思是结构的对齐要求将不同于 char 缓冲区。 (我们还没有遇到任何问题。)。在这种情况下使用方案 2 应该没问题,因为临时缓冲区可以对齐 max_align_t,因此应该可以类型转换为任何结构。但这会导致复制整个消息 - 这可能会很大(比如几 K 大小)

  2. 有没有更好的方法来处理这种情况?

最佳答案

您的主要问题似乎是如何处理未对齐的消息。也就是说,如果每个消息结构的末尾没有足够的填充以使后续消息正确对齐,您可以通过将指向消息开头的指针重新解释为对象来触发未对齐的读取。

我们可以通过多种方式解决这个问题,也许最简单的方法是基于单字节指针的 ntoh,它始终有效对齐。

我们可以将令人讨厌的细节隐藏在包装类后面,包装类将采用指向消息开头的指针,并具有将 ntoh 适当字段的访问器。

如评论中所述,要求偏移量由 C++ 结构确定,因为消息最初是这样创建的,可能不会打包。

首先,我们的 ntoh 实现,已模板化,因此我们可以按类型选择一个:

template <typename R>
struct ntoh_impl;

template <>
struct ntoh_impl<uint16_t>
{
static uint16_t ntoh(uint8_t const *d)
{
return (static_cast<uint16_t>(d[0]) << 8) |
d[1];
}
};

template <>
struct ntoh_impl<uint32_t>
{
static uint32_t ntoh(uint8_t const *d)
{
return (static_cast<uint32_t>(d[0]) << 24) |
(static_cast<uint32_t>(d[1]) << 16) |
(static_cast<uint32_t>(d[2]) << 8) |
d[3];
}
};

template<>
struct ntoh_impl<uint64_t>
{
static uint64_t ntoh(uint8_t const *d)
{
return (static_cast<uint64_t>(d[0]) << 56) |
(static_cast<uint64_t>(d[1]) << 48) |
(static_cast<uint64_t>(d[2]) << 40) |
(static_cast<uint64_t>(d[3]) << 32) |
(static_cast<uint64_t>(d[4]) << 24) |
(static_cast<uint64_t>(d[5]) << 16) |
(static_cast<uint64_t>(d[6]) << 8) |
d[7];
}
};

现在我们将定义一组讨厌的宏,通过在结构 proto(每个类的私有(private)结构)中查找具有匹配名称的成员,自动实现给定名称的访问器:

#define MEMBER_TYPE(MEMBER) typename std::decay<decltype(std::declval<proto>().MEMBER)>::type

#define IMPL_GETTER(MEMBER) MEMBER_TYPE(MEMBER) MEMBER() const { return ntoh_impl<MEMBER_TYPE(MEMBER)>::ntoh(data + offsetof(proto, MEMBER)); }

最后,我们有一个您给出的消息结构的示例实现:

class Message
{
private:
struct proto
{
uint32_t something1;
uint16_t something2;
};

public:
explicit Message(uint8_t const *p) : data(p) {}
explicit Message(char const *p) : data(reinterpret_cast<uint8_t const *>(p)) {}

IMPL_GETTER(something1)
IMPL_GETTER(something2)

private:
uint8_t const *data;
};

现在 Message::something1()Message::something2() 已经实现,并且会同时从 data 指针读取偏移它们最终位于 Message::proto 中。

在 header 中提供实现(有效地内联)有可能在每个访问器的调用站点内联整个 ntoh 序列!

此类不拥有构造它的数据分配。如果此处有所有权维护详细信息,您大概可以编写一个基类。

关于使用调度程序的 C++ NTOH 转换 - 事件队列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43331268/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com