【问题标题】:In C++, can I find in a data structure without generating the whole data structure?在 C++ 中,我可以在不生成整个数据结构的情况下找到数据结构吗?
【发布时间】:2015-09-26 21:30:59
【问题描述】:

我有一个想要优化的函数,因为它占用了我程序运行时间的 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();
}

【问题讨论】:

  • 您可以尝试在Bar::generate() 中使用static 向量,并返回const vector&lt;Foo&gt; &amp;
  • 这是个好主意!那可能会有很大帮助。我试试看。
  • 如果您想要两个用例的不同语义,我认为不同的功能绝对是要走的路。因此,类似于 generate_and_find(pred) 和 generate(),在两者都调用的 generate-element 函数或构造函数中具有公共代码。
  • 您可以将my_pred 传递给generate 函数并让它调用它来计算它应该生成什么。
  • 您所有问题的答案都是肯定的,但实施它们需要真正了解您是如何生成数据的,所以我认为您没有提供足够的信息。

标签: c++ optimization


【解决方案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);
}

【讨论】:

  • 这是个好主意。它让我避免了分配,但不会产生不必要的元素。在我的情况下这是完美的,因为分配比一代更昂贵。如果有人给出一个让我避免生成和分配的答案,我会接受,否则我会接受这个。
【解决方案2】:

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

首先,泛化生成器:

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 'add' 是正确的:

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

对于您的用户,您可以使用 Storages 提供 Bar 版本:

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 时,它会过早停止。

【讨论】:

    【解决方案3】:

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

    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.

    【讨论】:

      猜你喜欢
      • 2021-11-04
      • 1970-01-01
      • 2015-08-03
      • 2012-05-01
      • 1970-01-01
      • 2020-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多