【问题标题】:How to save memory when many unordered_map<string, double>s have exactly the same string set as key当许多 unordered_map<string, double> 具有完全相同的字符串设置为键时如何节省内存
【发布时间】:2016-08-12 07:13:31
【问题描述】:

我正在使用特征进行分类。

每个功能组都是unordered_map&lt;string, double&gt;string 是特征名称,double 是特征值。

class FeatureGroup {
      private:
        unordered_map<string, double> features_ = unordered_map<string, double>{
                { "c_n_a", 0 },
                { "c_n_b", 0 },
                { "l_1_a_1mm", 0 },
                { "l_2_a_1mm", 0 },
                { "l_3_a_1mm", 0 },
                ...
            }
    }

每个实例都有一个功能组。 而且,我有很多(比如说 8000000)个实例。

我的问题是:我想不费吹灰之力地节省内存。正如你所说,我已经在使用简短的功能名称了。

由于每个实例的特征名称在实验中是相同的,我不希望像“c_n_a”、“c_n_b”这样的特征名称字符串被存储 8000000 次。

我已经做了一些搜索(比如使用 char* 作为 Key 类型,std::reference_wrapper),但还是一头雾水。所以,请帮忙。我应该怎么做才能不存储功能名称 8000000 次从而节省内存?

PS:

我阅读了有关flyweight 的内容,但没有发现它不应该工作。但是,在我如下更改代码后,我的程序变得非常缓慢。

using flyweight_string = boost::flyweight<std::string>;

class FeatureGroup {
    private:
        unordered_map<flyweight_string, double> features_ = unordered_map<flyweight_string, double>{
                { flyweight_string("c_n_a"), 0 },
                { flyweight_string("c_n_b"), 0 },
                { flyweight_string("l_1_a_1mm"), 0 },
                { flyweight_string("l_2_a_1mm"), 0 },
                { flyweight_string("l_3_a_1mm"), 0 },
                { flyweight_string("l_1_b_1mm"), 0 },
                ...
        }
}

在设置和获取特征时,我使用如下格式:

features_[flyweight_string(feature_name)] // feature_name is of string type

在设置特征值的时候,我也使用了下面这句话来检查特征名称是否被定义。如果没有,程序exit(1)

if(features_.find(flyweight_string(feature_name)) != features_.end())   

我的程序结构如下。我希望有人能找到使用 boost::flyweight 后变慢的原因。

在我的程序中,每个 Instance(类)都有一个 ID、FeatureGroup 和类标签。我有另一个名为InstanceManager 的类,它实际上维护了一个实例容器(即unordered_set&lt;Instance&gt;)。 在我的程序中,我为所有实例计算每个特征,例如一次为所有实例计算"c_n_a",然后更新存储在容器中的相应特征值。在计算完所有特征值之后,我得到每个实例的特征值,并使用经过训练的模型来预测类标签。

实例特征值的设置和获取使用OpenMP为实例容器并行化。

在 Windows 性能监视器中,在更改为 boost::flyweight&lt;std::string&gt; 之前,所有 CPU 内核的利用率几乎为 100%。改成蝇量级后,CUP利用率下降到6~7%。毕竟,我的程序变得非常慢。

我不知道为什么由于从string 更改为flyweight_string,并行化无法正常工作。还有,如何解决?

【问题讨论】:

  • 使用字符串数组来存储特征名称并将其声明为常量。
  • 你查看trie了吗?
  • 您的问题与标题不符。您不能拥有与地图中的键完全相同的字符串,除非它是多地图,但事实并非如此。
  • 所以你的键总是一样的,但是值会随着实例的变化而变化——是这样吗?像这样: map features_one = { {"key1":1 },{"key2":0.3 },{"key3":0.2 },{"key4":3 }} ; map features_two = { {"key1":0.1 },{"key2":0.35 },{"key3":0.12 },{"key4":0.33 }} 。是这样吗?
  • @MiroRodozov ,正如您所说,键始终相同,只是值因实例而异。我选择使用 unordered_map 而不是 vector 来表示特征值,因为我想节省打字工作并使特征值的访问更加直观。

标签: c++ string memory unordered-map


【解决方案1】:

编辑

底部是答案的原始内容,但随着问题的更新,我正在完全修改它。您可以将代码修改为

class FeatureGroup {
  private:
    enum{
        c_n_a=0,
        c_n_b,
        ...
        num_features};
    std::vector<double> features_;
}

您应该使用features(num_features) 初始化features。例如,要访问c_n_b 对应的功能,只需使用features_[c_n_b]

这是您可以得到的最有效的方法。事实上,您甚至不需要尝试缩短功能名称。


flyweight design pattern 解释为

在计算机编程中,享元是一种软件设计模式。享元是一种通过与其他类似对象共享尽可能多的数据来最小化内存使用的对象;当简单的重复表示会使用不可接受的内存量时,这是一种大量使用对象的方法。

这里似乎很容易使用boost::flyweight

#include <iostream>
#include <unordered_map>

#include <boost/flyweight.hpp>

using fly_str = boost::flyweight<std::string>;

int main()
{   
    std::unordered_map<fly_str, int> m;
    m[fly_str("hello")] = 2;                                                                                                               
}   

【讨论】:

  • 我试过 boost::flyweight,但是我的程序很慢,你能帮忙找出原因吗? (见我帖子里的PS部分)
  • 另外,我有一个困惑。我使用的简短功能名称非常短,以至于我想知道通过 boost::flyweight<:string> 可以节省多少内存。因为虽然存储的是hash值而不是真正的字符串,hash值是64位的(我的程序是64位windows程序)。
  • 我想知道使用 flyweight 是否会使线程互相等待读取背景字符串。顺便说一句,我正在使用 OpenMP。因为改成享元后,每个核心的CUP利用率变得非常非常低(总共6~7%)。
  • @JohnSmith 您使用 OpenMP(我认为这是对您问题的后期编辑),由于缓存行,确实可能会改变一些事情。我会稍微研究一下。
【解决方案2】:

看起来您可以将功能名称硬编码到源代码中。如果是这样,您根本不应该使用字符串 - 改用枚举:

enum class FeatureName { c_n_a, c_n_b, l_1_a_1mm, l_2_a_1mm, l_3_a_1mm, ... };

class FeatureGroup {
      private:
        std::unordered_map<FeatureName, double> features_ =
            std::unordered_map<FeatureName, double> {
                { FeatureName::c_n_a, 0 },
                { FeatureName::c_n_b, 0 },
                { FeatureName::l_1_a_1mm, 0 },
                { FeatureName::l_2_a_1mm, 0 },
                { FeatureName::l_3_a_1mm, 0 },
                ...
            }
    }

您可能需要在FeatureName 和字符串之间进行转换的函数。有很多关于如何做到这一点的例子。请注意,枚举数的长度对程序的内存消耗没有影响,因此您可以根据需要制作它们以方便阅读。

【讨论】:

  • 感谢所有分享想法的人。 Ami Tavory 的 flyweight 方式不适用于 OpenMP 并行化,我仍然没有找到原因。此外,Ami Tavory 使用枚举作为特征向量索引的想法很酷。这种技术应该可以节省大量 RAM。不过考虑到代码修改量,我还是选择了Martin Bonner的方式。由于容器的类型变化不大,我可以节省很多人工。
【解决方案3】:

您可以创建一个中间查找,将您的字符串键转换为一个数字,然后将其存储为键。

此函数可以有一个向量,其中向量中的字符串键索引将是结果数字键。如果字符串键不在向量中,则将其插入末尾,并返回此键索引。这种方法的问题是查找需要 O(n)。或者,您可以将数字存储在地图中,它们的键是字符串键。

向量方法:

int StringKeyToNumber(vector<string>& lookup, const string& strKey) {
    auto it = find(begin(lookup), end(lookup), strKey);
    if (it != end(lookup)) {
        return distance(begin(lookup), it);
    }
    lookup.push_back(strKey);
    return look.size() - 1;
}

地图方法:

int StringKeyToNumber(map<string, int>& lookup, const string& strKey) {
    auto it = lookup.find(strKey);
    if (it != end(lookup)) {
        return it->second;
    }
    int newIndex = lookup.size();
    lookup[strKey] = newIndex;
    return newIndex;
}

我不确定使用char* 作为键类型,虽然它会降低内存要求,但会是一个好的解决方案。很容易让两个字符串具有相同的内容但位于不同的内存位置。

实际上你想要一个你可以断言的 has 值只代表一个字符串表示,这样你只需要存储哈希值。上述解决方案为您提供了保证(至少对于前 2147483647 个字符串):)

【讨论】:

    【解决方案4】:

    假设标题说“与键设置完全相同的字符串”,您可以先创建单个映射:

    map<string,int> myKeyToPositionMap = {
    { "c_n_a", 1 }, { "c_n_b", 2 },{ "l_1_a_1mm", 3 },{ "l_2_a_1mm", 4 },{ "l_3_a_1mm", 5 }};
    

    并用向量替换FeatureGroup中的地图

    class FeatureGroup {
          private:
    vector<double> features_ = {0.2,0.1,0.3,0.5}; };
    

    这样你只得到一张地图,你可以从中得到相应值在那个向量中的位置,比如说你想得到 c_n_b 的值,

    int keyForCNB = myKeyToPositionMap.find("c_n_b");
    double valueForCNB = featuresGroupInstance->getFeaturesVector.at(keyForCNB);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-09
      • 1970-01-01
      • 2011-02-22
      • 2023-03-15
      相关资源
      最近更新 更多