与 VS2013 相比,gcc 4.7.2 中的 std::map 实现效率非常低?

Posted

技术标签:

【中文标题】与 VS2013 相比,gcc 4.7.2 中的 std::map 实现效率非常低?【英文标题】:std::map implementation very inefficient in gcc 4.7.2 compared to VS2013? 【发布时间】:2013-11-04 10:31:45 【问题描述】:

有人可以帮我理解 std::map 容器是如何实现的吗?我有一个包含原子成员的类,我不需要调用复制构造函数,因此我使用 c++11 删除运算符来抑制复制构造函数的隐式生成。

MyCalss(const MyClass& a) = delete;

这在我的 windows 版本中运行良好,但是在 Linux 中我收到一个错误,通知我 std::map 类的 [] 运算符正在尝试调用已删除的函数。

Windows VS2013 和 Linux GCC 4.7.x 的 map 实现之间似乎存在重大差异。这让我做了一个关于如何将对象插入地图的实验。

我写了这个小例子程序:

#include <stdlib.h>
#include <stdio.h>
#include <map>
#include <iostream>
#include <string>

using namespace std;
class TestItem 
public:
TestItem ()  
    _name = "TestItem" + id();
    cout << "Constructing " << _name << endl;

TestItem (const TestItem & other) 
   _name = "TestItem " + id();
   cout << "Copying " << other._name << " to new  " << _name <<endl;


string id() 

   static int id = 0;
   char buf[2];
   sprintf_s(buf, "%d", id++);
   return string(buf);

~TestItem()
   cout << "Destroying " << _name << endl;

void doStuff()

   // stub


string _name;
;

void run()

   cout << "making new obj" << endl;
   TestItem a;
   cout << endl << endl;

   map<string, TestItem> TestItemMap;
   cout << "Makeing new obj as part of a map insert" << endl;
   TestItemMap["foo"].doStuff();
   cout << endl << endl;

   cout << "adding a value to the map" << endl;
   TestItemMap["new foo key"] = a;
   cout << endl << endl;

   cout << "looking up a value that has already been inserted" << endl;
   TestItem& b = TestItemMap["foo"];
   cout << endl << endl;

int main(int argc, char** argv)

   run();

在 Windows 中,当我运行这个程序时,我得到以下输出:

making new obj
Constructing TestItem0

Making new obj as part of a map insert
Constructing TestItem1

adding a value to the map
Constructing TestItem2

looking up a value that has already been inserted

Destroying TestItem1
Destroying TestItem0
Destroying TestItem0

这是我在内部编写时希望看到的

 TestItemMap["foo"].doStuff();

我希望地图会创建一个新的 TestItem 实例,然后通过将树节点内部链接到新的 TestItem 将其插入 RedBlack 树。

但是,当我在 Linux 中运行相同的代码时,结果却大不相同

making new obj
Constructing TestItem0

Making new obj as part of a map insert
Constructing TestItem1
Copying TestItem1 to new TestItem2
Copying TestItem2 to new TestItem3
Destroying TestItem2
Destroying TestItem1

adding a value to the map
Constructing TestItem4
Copying TestItem4 to new TestItem5
Copying TestItem5 to new TestItem6
Destroying TestItem5
Destroying TestItem4

looking up a value that has already been inserted

Destroying TestItem0
Destroying TestItem3
Destroying TestItem0

这将向我表明 [] 运算符正在创建 TestItem 的新实例,然后调用外部 map.insert() 函数,然后销毁新创建的 TestItem,这仅解释了对复制构造函数的调用之一. gcc中的c++ stdlib真的这么低效吗?

人们使用一些标准技巧来解决这个问题吗?

【问题讨论】:

GCC默认使用的标准库叫做libstdc++,是开源的。也附带 GCC 源代码。如果您想知道它是如何实现的,只需下载源代码并查看即可。 您正在比较启用优化的两个编译,不是吗? 至于你的问题,我会更好奇说明TestItem0被破坏的两行行。 sprintf_s 不是标准的,所以它不能在 linux 上编译。你用了什么代码? @JoachimPileborg 这是用于 TestItemMap["new mooky key"] = a; 的显然未考虑的赋值运算符的结果,我敢打赌 OP 没有看到它的到来。 【参考方案1】:

首先,我修复了那个可怕的sprintf_s 东西:

string id() 

   static int id = 0;
   std::stringstream s;
   s << id++;
   return s.str();

并且还更改了您的“查找已插入的值”以实际执行它所说的[编辑:你也是:-)]

现在,在 C++03 模式下使用 g++ 4.8.1 进行编译,我得到的结果与您的相似。但是用-std=c++11编译,我得到了

making new obj
Constructing TestItem0

Making new obj as part of a map insert
Constructing TestItem1

adding a value to the map
Constructing TestItem2

looking up a value that has already been inserted

Destroying TestItem0
Destroying TestItem1
Destroying TestItem0

似乎 MSVC 会自动使用 C++11 特性(最有可能是移动语义)来提供很好的性能提升,而您需要明确告诉 g++ 也这样做。

【讨论】:

【参考方案2】:

这似乎是 GCC 4.8 修复的错误。

Here 它适用于 GCC 4.8 Here GCC 4.7 编译失败

【讨论】:

我敢说这不是 GCC 错误,而是 MSVC 无法正常工作。存在用户声明的复制构造函数时,不会生成隐式声明的移动构造函数。因此,GCC 不应该因为不能使用移动语义而受到责备——用户已经告诉它不要这样做。 (不要问我为什么 4.8 版“有效”) @Damon 新元素默认就地构造,然后赋值,我实际上看不到元素在哪里/应该被复制。错误 AFAICT 是 GCC 4.7 在实例化 operator[] 时添加新元素时在内部需要复制可构造性,这是标准不要求/不允许的。 @Damon 我认为它很可能是 std::map 内部的一个内部 Node 结构(或类似的东西)正在被移动构造,其效果是避免了用户的复制构造函数. @TristanBrindle 不,不需要复制或移动构造函数,节点应使用默认构造函数并在适当位置构造新元素。这就是 VC 和 GCC 4.8 正在做的事情。然后,他们从 operator[] 返回对这个新元素的引用,并进行分配,请参阅我链接的示例。

以上是关于与 VS2013 相比,gcc 4.7.2 中的 std::map 实现效率非常低?的主要内容,如果未能解决你的问题,请参考以下文章

LinuxC++开发面试系列:代码运行gcc编译system函数与VS2013 C4996解决方法

LinuxC++开发面试系列:代码运行gcc编译system函数与VS2013 C4996解决方法

LinuxC++开发面试系列:代码运行gcc编译system函数与VS2013 C4996解决方法

与 2013 年相比,安装过剩

将解决方案从 .net framework 4 升级到更高版本。 4.7.2; 4.8.VS2013

与 gcc 相比,comeau 编译器值得吗?