我们正在用C到C ++重写我们的遗留代码。在我们系统的核心,我们有一个TCP客户端,它连接到master。 Master将持续不断地传输消息。每个套接字读取将产生N个格式的消息 - {type, size, data[0]}
。
现在我们不会将这些消息复制到单个缓冲区中 - 只需将指针传递给消息的开头,将指向缓冲区的length_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);
然后使用消息。
跟踪新代码的登录有几个问题。
由于我们正在向不同的工作人员发送消息,因此每个进行ntoh转换的工作人员都会导致缓存未命中问题,因为消息没有缓存对齐 - 即消息没有填充。
相同的消息可以由不同的工作人员处理 - 这种情况是消息需要在本地处理并且还传递给另一个进程。这里,中继工作者需要原始网络顺序中的消息,并且本地工作需要转换为主机顺序。显然,由于信息不重复,两者都无法满足。
我想到的解决方案是 -
复制邮件并为所有中继工作人员发送一份副本(如果有)。在调度之前对调度程序本身中属于同一缓冲区的所有消息进行ntoh转换 - 例如调用handler->ntoh(message);
以解决缓存未命中问题。
向每位工作人员发送原始副本。每个工作人员都会将消息复制到本地缓冲区,然后执行ntoh转换并使用它。这里每个worker都可以使用特定于线程的(thread_local)静态缓冲区作为便笺来复制消息。
现在我的问题是
是选项1进行ntoh转换的方法 - C ++ sy?我的意思是结构的对齐要求与char缓冲区不同。 (我们还没有遇到任何问题。)在这种情况下,使用方案2应该没问题,因为临时缓冲区可以具有max_align_t的对齐,因此可以对任何结构进行类型转换。但是这会导致复制整个信息 - 这可能相当大(比如几个K大小)
有没有更好的方法来处理这种情况?
答案 0 :(得分:1)
您的主要问题似乎是如何处理未对齐的邮件。也就是说,如果每个消息结构在其末尾没有足够的填充以便以下消息正确对齐,则可以通过将指向消息开头的指针重新解释为对象来触发未对齐的读取。
我们可以通过多种方式解决这个问题,也许最简单的方法是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];
}
};
现在我们将定义一组令人讨厌的宏,它们将通过在struct proto
中查找具有匹配名称的成员(每个类的私有结构)来自动实现给定名称的访问器:
#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
的相同偏移量。
在标题中提供实现(有效内联)有可能在每个访问者的调用站点内联整个ntoh
序列!
此类不拥有从中构造的数据分配。据推测,如果这里有所有权维护的详细信息,你可以写一个基类。