如何在<Mat>opencv c++中有效地清除向量

How to efficiently clear vector<Mat> in opencv c++

本文关键字:c++ 向量 有效地 opencv 清除 Mat lt gt      更新时间:2023-10-16

我有一个vector<Mat> my_vect,每个Mat都是浮动的,它们的大小是90*90。我开始从磁盘加载矩阵,我将16000个矩阵加载到那个向量中。在我完成对这些矩阵的处理后,我会清除它们。这是我加载和清除矢量的代码:

Mat mat1(90,90,CV_32F);
load_vector_of_matrices("filename",my_vect); //this loads 16K elements
//do something
for(i = 1:16K)
    correlate(mat1, my_vect.at(i));
my_vect.clear();

为了效率,我把16K元件装在一起。

现在我的问题是,阅读所有这些矩阵需要3-4秒,而my_vect.clear()需要大约相同的时间,这是一个很大的时间。

根据这个答案,应该需要O(n)的时间,我假设vector<Mat>没有一个琐碎的析构函数。

为什么清除需要这么长时间,矩阵析构函数会覆盖矩阵中的每个索引吗有没有办法减少清除矢量的时间

编辑:我使用的是Visual Studio 2010,优化级别是最大化速度(/O2)。

首先,一个流加载程序。为它提供一个函数,在给定最大值的情况下,返回一个数据向量(也称为loader<T>)。它可以存储内部状态,但会被复制,所以将该内部状态存储在std::shared_ptr中。我保证只会调用它的一个副本。

您不负责从加载程序返回所有max数据,但根据编写,您必须至少返回一个元素。返回更多是肉汁,可以减少线程开销。

然后拨打streaming_loader<T>( your_loader, count )

它返回一个std::shared_ptr< std::vector< std::future< T > > >。您可以等待这些期货,但您必须按顺序等待(在第一个期货提供数据之前,不能保证第二个期货准备好等待)。

template<class T>
using loader = std::function< std::vector<T>(size_t max) >;
template<class T>
using stream_data = std::shared_ptr< std::vector< std::future<T> > >;
namespace details {
  template<class T>
  T streaming_load_some( loader<T> l, size_t start, stream_data<T> data ) {
    auto loaded = l(data->size()-start);
    // populate the stuff after start first, so they are ready:
    for( size_t i = 1; i < loaded.size(); ++i ) {
      std::promise<T> promise;
      promise.set_value( std::move(loaded[i]) );
      (*data)[start+i] = promise.get_future();
    }
    if (start+loaded.size() < data->size()) {
      // recurse:
      std::size_t new_start = start+loaded.size();
      (*data)[new_start] = std::async(
        std::launch::async,
        [l, new_start, data]{return streaming_load_some<T>( l, new_start, data );}
      );
    }
    // populate the future:
    return std::move(loaded.front());
  }
}
template<class T>
stream_data< T >
streaming_loader( loader<T> l, size_t n ) {
  auto retval = std::make_shared<std::vector< std::future<T> >>(n);
  if (retval->empty()) return retval;
  retval->front() = std::async(
    std::launch::async,
    [retval, l]()->T{return details::streaming_load_some<T>( l, 0, retval );
  });
  return retval;
}

为了使用,您获取stream_data<T>(也称为指向未来数据向量的共享指针),对其进行迭代,然后依次使用.get()。然后进行处理。如果你需要一个由50个组成的区块,依次调用每个区块的.get(),直到达到50——不要跳到第50个。

这是一个完全玩具装载机和测试线束:

struct loader_state {
    int index = 0;
};
struct test_loader {
  std::shared_ptr<loader_state> state; // current loading state stored here
  std::vector<int> operator()( std::size_t max ) const {
    std::size_t amt = max/2+1;// really, really stupid way to decide how much to load
    std::vector<int> retval;
    retval.reserve(amt);
    for (size_t i = 0; i < amt; ++i) {
      retval.push_back( -(int)(state->index + i) ); // populate the return value
    }
    state->index += amt;
    return retval;
  }
  // in real code, make this constructor do something:
  test_loader():state(std::make_shared<loader_state>()) {}
};
int main() {
  auto data = streaming_loader<int>( test_loader{}, 1024 );
  std::size_t count = 0;
  for( std::future<int>& x : *data ) {
    ++count;
    int value = x.get(); // get data
    // process.  In this case, print out 100 in blocks of 10:
    if (count * 100 / data->size() > (count-1) * 100 / data->size())
      std::cout << value << ", ";
    if (count * 10 / data->size() > (count-1) * 10 / data->size())
      std::cout << "n";
  }
  std::cout << std::endl;
  // your code goes here
  return 0;
}

CCD_ 13可能没有价值,也可能没有价值。上面加载程序的内部状态非常没有价值,我只是用它来演示如何存储一些状态。

您可以执行类似于销毁一堆对象的操作,而无需等待它们的销毁程序完成。或者,您可以相信,在处理数据并等待加载下一个数据时,可能会破坏数据。

实例

在一个工业强度的解决方案中,你需要包括放弃所有这些东西的方法。例外可能是一种方式。此外,向加载程序反馈处理代码落后的程度也会很有帮助(如果它紧随其后,则返回较小的块——如果它远远落后,则返回较大的块)。理论上,这可以通过loader<T>中的反向信道来安排。

现在我已经用上面的方法玩了一段时间,可能更好的方法是:

#include <iostream>
#include <future>
#include <functional>
#include <vector>
#include <memory>
// if it returns empty, there is nothing more to load:
template<class T>
using loader = std::function< std::vector<T>() >;
template<class T>
struct next_data;
template<class T>
struct streamer {
  std::vector<T> data;
  std::unique_ptr<next_data<T>> next;
};
template<class T>
struct next_data:std::future<streamer<T>> {
    using parent = std::future<streamer<T>>;
    using parent::parent;
    next_data( parent&& o ):parent(std::move(o)){}
};

活生生的例子。它需要一些基础设施来填充第一个streamer<T>,但代码会更简单,而且奇怪的要求(知道多少数据,只从第一个元素中执行.get())也会消失。

template<class T>
streamer<T> stream_step( loader<T> l ) {
    streamer<T> retval;
  retval.data = l();
  if (retval.data.empty())
    return retval;
  retval.next.reset( new next_data<T>(std::async( std::launch::async, [l](){ return stream_step(l); })));
  return retval;
}
template<class T>
streamer<T> start_stream( loader<T> l ) {
  streamer<T> retval;
  retval.next.reset( new next_data<T>(std::async( std::launch::async, [l](){ return stream_step(l); })));
  return retval;
}

缺点是编写基于范围的迭代器变得有点棘手。

以下是第二种实现的示例用法:

struct counter {
  std::size_t max;
  std::size_t current = 0;
  counter( std::size_t m ):max(m) {}
  std::vector<int> operator()() {
    std::vector<int> retval;
    std::size_t do_at_most = 100;
    while( current < max && (do_at_most-->0)) {
      retval.push_back( int(current) );
      ++current;
    }
    return retval;
  }
};
int main() {
  streamer<int> s = start_stream<int>( counter(1024) );
  while(true) {
    for (int x : s.data) {
      std::cout << x << ",";
    }
    std::cout << std::endl;
    if (!s.next)
      break;
    s = std::move(s.next->get());
  }
  // your code goes here
  return 0;
}

其中CCD_ 17是一个琐碎的加载器(一个将数据以任何大小的块读取到CCD_ 18中的对象)。数据的处理在main代码中,我们只需将它们打印成大小适中的块。

加载发生在另一个线程中,无论主线程做什么,都将异步继续。主线程只是得到了std::vector<T>,以便按照他们的意愿进行处理。在你的情况下,你会把T变成Mat

Mat对象是具有内部内存分配的复杂对象。清除向量时,需要遍历包含的Mat的每个实例,并运行其析构函数,这本身就是一个非平凡的操作。

还要记住,自由存储内存操作是不平凡的,因此根据您的堆实现,堆可能会决定合并单元格等。

如果这是一个问题,您应该通过探查器运行clear,并找出瓶颈所在。

小心使用优化,它会让调试器发疯。如果你在一个函数中这样做,只是让向量超出范围??由于元素不是指针,我认为这会起作用。