在C ++中,我可以在数据结构中找到而不生成整个数据结构吗?

时间:2015-09-26 21:30:59

标签: c++ optimization

我有一个我想优化的功能,因为它需要13.4%的程序运行时间。

此函数返回一个相当大的容器,但大多数调用者不需要整个容器,因为它们只是在数据结构中搜索符合特定条件的元素,然后抛出容器。但是,有一些调用者使用整个容器。此外,返回的容器具有众所周知的最大大小,并且在每次调用函数时通常都非常接近该大小。

我希望优化此功能的一种方法是,当调用者只需要搜索特定项目时,不会生成整个数据结构,因为这样可以为这些调用者节省大约一半的时间,因为始终包含搜索的项目。是否可以这样做,并且对于需要整个容器的调用者仍然具有相同的功能?或者,我可以实现一个适用于一种类型的调用者的函数,另一个适用于其他类型的调用者,但让它们以某种方式共享逻辑吗?这就是整个设置的样子:

我想要优化的功能:

vector<Foo> Bar::generate() const {
    vector<Foo> results; //Using a vector is arbitrary, it could be any container
    results.reserve(100);
    int n = 100;
    while (n > 0 && this->shouldGenerate(n)) {
        n--;
        results.emplace_back(...);
    }
    return results
}

最常见的来电者:

Foo baz(Bar bar) {
    vector<Foo> items = bar.generate();
    auto it = find_if(items.begin(), items.end(), my_pred);
    if (it == items.end()) {
        return Foo();
    } else {
        return *it;
    }
}

不太常见的来电者:

void Qux::storeGeneratedFoos(Bar bar) {
    this->foos = bar.generate();
}

3 个答案:

答案 0 :(得分:1)

您可以尝试在函数中使用static向量,以避免每次重新分配空间,并返回对它的引用:

const vector<Foo> &Bar::generate() const {
    static vector<Foo> results; //Using a vector is arbitrary, it could be any container
    results.clear(); //clear from possible previous invocation
    ...
}

然后baz()可以定义为

Foo baz(Bar bar) {
    const vector<Foo> &items = bar.generate();
    ...
}

此解决方案无需修改其他功能。

要减少代数,可以按如下方式创建模板函数:

template<class UnaryPredicate>
Foo Bar::generate_if(UnaryPredicate pred) const {
    Foo foo;
    int n = 100;
    while (n > 0 && this->shouldGenerate(n)) {
        n--;
        //instead of 'results.emplace_back(...);' do
        foo = ...;
        if (pred(foo))
            return foo;
    }
    return Foo();
}

并将baz()的定义更改为

Foo baz(Bar bar) {
    return bar.generate_if(my_pred);
}

答案 1 :(得分:1)

我建议区分两种用例:这样,你可以更快地获得。由于运行时似乎是一个问题,我会尽量提高效率。

首先,概括发电机:

template <typename Storage>
void Bar::generate_impl(Storage & storage) const {
    for (int n = 100; 
         n > 0 && shouldGenerate(n) and storage.go_on(); 
         --n) {
        storage.add(/* some newly built Foo */);
    }
}

然后你可以有两种Storage。第一个是创建vector的原始用例。如您所见,它不会过早停止并存储每个传递的Foo

struct MemorizingStorage {
    vector<Foo> data;
    MemorizingStorage() { data.reserve(100); }
    void add(Foo const & f) { data.emplace_back(f); }
    bool go_on() const { return true; }
}; // MemorizingStorage

第二个将用于检查是否生成了一些Foo的用例。此版本不会存储任何内容,但请记住是否添加了Foo''是正确的:

struct CheckingStorage {
    Foo const & item;
    bool found_it;
    CheckingStorage(Foo const & f) : item(f), found_it(false) {}
    void add(Foo const & f) { found_it = found_it or (item == f); }
    bool go_on() const { return not found_it; }
}; // CheckingStorage

对于您的用户,您使用Bar s:

提供Storage个版本
vector<Foo> Bar::generate() const {
    MemorizingStorage storage;
    generate_impl(storage);
    return storage; // consider std::move if C++11 is applicable
}

bool Bar::is_generated(Foo const & item) const {
    CheckingStorage storage(item);
    generate_impl(storage);
    return storage.found_it;
}

这是我能想到的最快的方式:

  • 不需要时不需要创建任何矢量。
  • 当用户只想知道某个Foo是否已生成时,它会提前停止。

答案 2 :(得分:1)

一个选项是创建自定义迭代器以从Bar获取项目。然后你可以直接在find_if中使用自定义迭代器,或者你可以使用它来使用两个迭代器构造函数或vector直接初始化assign项:

class BarIterator : public std::iterator<std::input_iterator_tag, Foo> {
  const Bar* bar;
  int n;
public:
  BarIterator(const Bar& bar, int n);
  Foo operator*() const;
  bool operator==(const BarIterator& other) const;
  bool operator!=(const BarIterator& other) const;
  BarIterator& operator++(){ n--; return *this; }
};

class Bar {
  ...
public:
  BarIterator begin() const { return {*this, 100}; }
  BarIterator end() const { return {*this, 0}; }
  friend class BarIterator;
};

Foo baz(const Bar& bar) {
    auto it = std::find_if(bar.begin(), bar.end(), my_pred);
    if (it == bar.end()) {
        return Foo();
    } else {
        return *it;
    }
}

class Quz {
  std::vector<Foo> foos;
public:
  void storeGeneratedFoos(const Bar& bar) {
    foos.assign(bar.begin(), bar.end());
  }
};

Live demo.