我们遇到一种奇怪的现象,其中包含头文件会导致某些内存分配密集型工作负载的性能下降5-10%.
此头文件将线程池声明为全局变量.此线程池从未在应用程序中的任何容量中使用.也就是说,除了在程序启动时创建这个静态线程池,应用程序完全是单线程的.删除标头后,性能损失将消失.
从一些研究来看,似乎多线程应用程序可能会因某些编译器优化不再可能而导致性能损失.每当与线程相关的构造以任何形式或容量实例化时,是否有可能关闭此类优化?
或者,由于在执行大量内存分配时性能损失似乎最明显,是否有可能在编译/链接阶段编译器意识到线程构造是实例化的,因此它切换到线程安全的内存分配器?
这种情况发生在Linux 64位工作站上,包括GCC和clang.正在使用来自C++ 11的标准线程原语.
编辑我也应该提一下,根据我们的测试,当使用tcmalloc分配器而不是默认分配器时,性能差异似乎消失了.
请考虑以下代码段:
#include <utility>
template <typename U>
auto foo() -> decltype(std::declval<U>() + std::declval<U>());
template <typename T>
decltype(foo<T>()) bar(T)
{}
int main()
{
bar(1);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这会在GCC的所有版本中触发警告和静态断言失败,我在编译时尝试了它(4.7.3,4.8.1,4.9-some-git)-Wall -Wextra
.例如,这是4.8.1的输出:
main.cpp: In instantiation of ‘decltype (foo<T>()) bar(T) [with T = int; decltype (foo<T>()) = int]’: main.cpp:12:7: required from here main.cpp:8:2: warning: no return statement in function returning non-void [-Wreturn-type] {} ^ In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.1/include/g++-v4/bits/move.h:57:0, from /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.1/include/g++-v4/bits/stl_pair.h:59, from /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.1/include/g++-v4/utility:70, from main.cpp:1: /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.1/include/g++-v4/type_traits: In instantiation of ‘typename …
C++ 11标准改变了erase()
标准容器方法的签名:它们现在接受const_iterator
s而不是iterator
s.本文档解释了该基本原理:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2350.pdf
现在,如果一个实现std::vector<T>
在一个简单的方法,就可以直接使用const T *
,并T *
分别为const和可变迭代器类型.所以在erase()
方法中我们可能有这样的代码:
iterator erase(const_iterator it)
{
...
for (; it != end() - 1; ++it) {
// Destroy the current element.
it->~T();
// Move-init the current iterator content with the next element.
::new (static_cast<void *>(it)) T(std::move(*(it + 1))); // FAIL
}
...
}
Run Code Online (Sandbox Code Playgroud)
现在的问题是,由于it
是一个const指针,静态强制转换将失败.
在这种情况下抛弃常量是否合法it
?请注意,it
永远不要指向一个const
对象(存储在向量中的对象永远不会const
),并且调用方法(erase()
)也不const
是这样.
编辑:感谢所有的回复.我想在这里回复一些评论. …
给定具有标准布局成员的标准布局类,例如:
struct foo {
int n;
int m;
unsigned char garbage;
};
Run Code Online (Sandbox Code Playgroud)
将它永远是安全的,按照标准,在该结构的最后一个字节写不写入的存储区域n
和m
(但可能写入garbage
)?例如,
foo f;
*(static_cast<unsigned char *>(static_cast<void *>(&f)) + (sizeof(foo) - 1u)) = 0u;
Run Code Online (Sandbox Code Playgroud)
花了一些时间阅读C++ 11标准后,在我看来答案可能是肯定的.
从9.2/15:
分配具有相同访问控制(第11条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址.未指定具有不同访问控制的非静态数据成员的分配顺序(11).实施对齐要求可能导致两个相邻成员不能立即分配; 因此,可能需要空间来管理虚拟功能(10.3)和虚拟基类(10.1).
因此,该garbage
成员具有比其他两个成员更高的地址(由于它们具有标准布局而连续存储),并且结构的最后一个字节因此必须属于garbage
或者是最终填充的一部分.
这个推理是否正确?我f
在这里干涉对象的生命吗?写入填充字节是一个问题吗?
编辑
在回复评论时,我在这里想要实现的是与我正在编写的类似于类的类.
如果以直接的方式进行(即,在变量类中放置一个int成员来记录存储的类型),填充将使该类几乎比它需要的大50%.
我要做的是确保我将要存储在变体中的每个类类型的每个最后一个字节都是可写的,因此我可以将存储标志合并到我正在使用的原始存储(对齐的原始字符数组)中变种.在我的具体情况下,这消除了大部分浪费的空间.
编辑2
作为一个实际示例,请考虑将这两个类存储在典型64位计算机上的变体中:
// Small dynamic vector class storing 8-bit integers.
class first {
std::int8_t *m_ptr;
unsigned short m_size_capacity; // Size and capacity packed into a single ushort.
};
// Vector class with …
Run Code Online (Sandbox Code Playgroud) 在我的代码的最新一轮重构中,我用可变参数对应的固定数量的模板参数替换了一堆模板类。当我发现一个特定的性能测试用例的性能下降了大约 20-30% 时,我感到非常困惑。
几次 git bisect 往返后,发现了违规提交。从字面上看,它由一个单一的变化组成
template <typename T, typename U>
class foo {};
Run Code Online (Sandbox Code Playgroud)
到
template <typename T, typename ... Args>
class foo {};
Run Code Online (Sandbox Code Playgroud)
我已经通过实验证实,应用这个单一的变化会导致上述减速。更令人费解的是,切换编译器版本(从 GCC 4.7 到 GCC 4.8)会将速度降低的发生移到另一个类似的提交(即,另一个从固定参数到可变参数的切换,但在不同的类中bar
)。
为了提供一些上下文,这个特定的性能测试用例是一个非常稀疏的计算机代数问题,它受内存限制,因此非常容易受到高效缓存内存利用的影响。这个测试用例在我的代码中一直是一个有问题的地方(例如,在 GCC 4.4/4.5 附近,我曾经不得不手动调整管理缓存行大小检测的编译器选项,以提取最大性能)。
有没有人知道是什么导致了这种行为?不幸的是,我担心提取简化的测试用例可能非常困难。
编辑
作为参考,这是恢复良好性能行为的提交。不幸的是,它包括对一堆类(而不是一个类)的恢复到非可变参数代码。我将尝试提出一个更局限的例子。
https://gitorious.org/piranhapp0x/mainline/commit/b952c613b42fe480fe4ed2dfd3e683eb9e38e4cd
在c ++ 0x中有没有办法从无序的关联容器中移出对象?我需要合并两个单独的无序集合,如果涉及到rvalues,我希望在即将停止的集合中"回收"项目.
事实是,unordered_set的迭代器只提供对存储项的const引用.我原本以为使用const_cast来抛弃常量,但是进一步阅读它似乎会导致未定义的行为.有什么建议吗?
编辑
拿这个简单的例子:
#include <string>
#include <unordered_set>
using namespace std;
void merge_sets(unordered_set<string> &s1, unordered_set<string> &&s2)
{
// Something like this, which (of course) does not work.
for (auto it = s2.begin(); it != s2.end(); ++it) {
s1.insert(std::move(*it));
}
}
int main()
{
unordered_set<string> s1, s2;
// Fill first set:
s1.insert("hello");
s1.insert("world");
// Fill second set.
s2.insert("foo");
merge_sets(s1,std::move(s2));
// After this operation, s2 is empty and s1 contains "hello", "world" and "foo".
}
Run Code Online (Sandbox Code Playgroud)
换句话说,我希望能够从s2移动项目而不是复制它们.应该有一种方法从一组中"提取"项目,使得项目被复制/移动然后从当前集合中删除.