您可能内存不足,bad_alloc 异常告诉您同样多。所以我不太明白混淆是什么:内存分配失败,抛出异常,如果分配失败,您的代码似乎没有多大意义,因此您没有捕获异常(因为那里没有合理的恢复操作),程序终止 - 一切都很好。分配更少的内存就不会出现这个问题,或者编译为 64 位。
还有其他效率低下的地方,所以让我们一一解决。
单独存储 sense_x 和 sense_y 值不是一个好主意:每个向量都会分配,因此每个粒子的分配量是两倍 并且它们在内存中不一定靠得很近,因此访问它们的缓存压力和延迟更高。将它们放在一个结构中,例如Point:
#include <iostream>
#include <random>
#include <type_traits>
#include <vector>
struct Point {
double x, y;
};
struct Particle {
int id;
Point pos;
double theta;
double weight;
std::vector<int> associations;
std::vector<Point> sense;
};
然后,您不需要复制权重:它们已经存在,因此您不妨提供一个“查看”它们的迭代器:
class const_weight_iterator : public std::vector<Particle>::const_iterator {
using It = std::vector<Particle>::const_iterator;
inline It &base() { return *this; }
inline const It &base() const { return *this; }
public:
using value_type = const double;
using pointer = value_type*;
using reference = value_type&;
reference operator*() const { return base()->weight; }
pointer operator&() const { return &(base()->weight); }
const_weight_iterator(const_weight_iterator &) = default;
const_weight_iterator(const It &iter) : It(iter) {}
};
我们使用它来创建粒子向量中权重的视图:
class as_const_weights {
const const_weight_iterator m_begin, m_end;
public:
as_const_weights(const std::vector<Particle> &v) :
m_begin(v.begin()), m_end(v.end()) {}
const_weight_iterator begin() const { return m_begin; }
const_weight_iterator end() const { return m_end; }
};
编写这样的代码很乏味,您可能想改用boost::make_transform_iterator - 这样您就不需要编写自己的迭代器:
class as_const_weights {
static auto get_weight(const Particle &particle) { return particle.weight; }
const std::vector<Particle>::const_iterator m_begin, m_end;
public:
as_const_weights(const std::vector<Particle> &v) :
m_begin(v.begin()), m_end(v.end()) {}
auto begin() const { return boost::make_transform_iterator(m_begin, get_weight); }
auto end() const { return boost::make_transform_iterator(m_end, get_weight); }
};
另一种选择可能是重用member_access_iterator from the lug project。
现在不需要复制权重了:
void ParticleFilter::resample() {
auto const_weights_view = as_const_weights(m_particles);
std::cout << "Weights: ";
for (auto weight : const_weights_view)
std::cout << weight << " ";
std::cout << std::endl;
std::discrete_distribution<size_t> distr(
const_weights_view.begin(), const_weights_view.end()
);
std::random_device rd;
auto const num_particles = m_particles.size();
std::cout << "New Particles: ";
std::vector<Particle> new_particles;
new_particles.reserve(num_particles);
for (int i=0; i<num_particles; i++){
int indice = distr(rd);
std::cout << indice << " ";
new_particles.push_back(m_particles[indice]); //*note
}
std::cout << std::endl;
m_particles = std::move(new_particles);
}
我们确切知道会有多少new_particles,因此reserved 可以有足够的存储空间,从而避免重复的内存重新分配以及由于new_particles 向量必须增长而导致的内存和复制开销。
由于new_particles 在方法返回后不会被使用,我们将它们移动到m_particles - 与m_particles = new_particles 所暗示的副本相比,这是一个便宜的操作。
*注意:我假设索引可以重复。因此,我们不能将粒子移动到new_particles 向量中,即new_particles.push_back(std::move(m_particles[indice])); 是错误的。但是,如果索引是唯一的(不要重复),那么移动将是合适的 - 但你真的想断言这个事实:
auto const num_particles = m_particles.size();
std::vector<Particle> new_particles;
new_particles.reserve(num_particles);
std::set<int> new_indices;
for (int i=0; i<num_particles; i++){
int indice = distr(rd);
std::cout << indice << " ";
assert(new_indices.find(indice) == new_indices.end());
// the index is unique
new_particles.push_back(std::move(m_particles[indice]));
new_indices.insert(indice);
}
我不确定您的确切应用程序是什么,以及您需要多久访问一次m_particles 数组,但我们还可以进一步做一步。请注意,resample 所做的只是创建索引映射。如果这个重采样的粒子数组不经常被访问,那么我们就不会意识到顺序访问的好处——它们会被复制粒子向量的成本所掩盖。
在这种情况下,我们只需要生成新索引的向量(而不是粒子),并拥有一个使用它的视图:
template <class Container, class Index> class indexed_view {
Container &m_container;
const Index &m_index;
using value_iter = typename Container::iterator;
using index_iter = typename Index::const_iterator;
using index_value = typename Index::value_type;
public:
using value_type = typename Container::value_type;
using size_type = typename Container::size_type;
class iterator {
Container &m_container;
index_iter m_it_index, m_end_index;
index_value m_val_index;
auto get_index() const { return m_it_index != m_end_index ? *m_it_index : index_value{}; }
public:
using value_type = typename value_iter::value_type;
using reference = typename value_iter::reference;
using pointer = typename value_iter::pointer;
iterator(Container &container, index_iter iter, index_iter end) :
m_container(container),
m_it_index(iter), m_end_index(end),
m_val_index(get_index())
{}
iterator(const iterator &) = default;
reference operator*() const { return m_container[m_val_index]; }
pointer operator&() const { return &(m_container[m_val_index]); }
auto &operator++() { ++m_it_index; m_val_index = get_index(); return *this; }
auto &operator--() { --m_it_index; m_val_index = get_index(); return *this; }
auto operator++(int) = delete;
auto operator--(int) = delete;
bool operator!=(const iterator &o) const { return m_it_index != o.m_it_index; }
};
indexed_view(Container &container, Index &index) :
m_container(container), m_index(index)
{}
auto size() const { return m_index.size(); }
iterator begin() { return iterator(m_container, m_index.begin(), m_index.end()); }
iterator end() { return iterator(m_container, m_index.end(), m_index.end()); }
auto &operator[](size_type index) { return m_container[m_index(index)]; }
};
template <class T1, class T2>
auto make_indexed_view(T1 &&container, T2 &&view) {
return indexed_view<std::decay_t<T1>, std::decay_t<T2>>(
std::forward<T1>(container), std::forward<T2>(view)
);
}
我认为 Boost 不提供类似的功能,当然 Ranges library 也不提供。
ParticleFilter 类现在如下所示:
class ParticleFilter {
using size_type = std::vector<Particle>::size_type;
std::vector<Particle> m_particles;
std::vector<size_type> m_sampledIndex;
public:
void resample();
void generateSampledIndex();
void processSampledParticles();
};
使用与粒子采样循环非常相似的循环生成采样索引,不同之处在于我们只存储新索引而不是存储粒子:
void ParticleFilter::generateSampledIndex() {
auto const_weights_view = as_const_weights(m_particles);
std::discrete_distribution<size_t> distr(
const_weights_view.begin(), const_weights_view.end()
);
std::random_device rd;
auto const num_particles = m_particles.size();
m_sampledIndex.clear();
m_sampledIndex.reserve(num_particles);
for (int i=0; i<num_particles; i++) {
int indice = distr(rd);
m_sampledIndex.push_back(indice);
}
}
现在可以轻松使用采样视图 - 原始粒子矢量不受干扰。
void ParticleFilter::processSampledParticles()
{
for (auto &particle : make_indexed_view(m_particles, m_sampledIndex)) {
// ...
}
}
在 C++20 中,make_indexed_view 不是必需的(感谢 HTNW!):
auto ParticleFilter::sampled_view() {
return
m_sampledIndex
| std::views::transform([&](auto i) -> auto& { return m_particles[i]; });
}
void ParticleFilter::processSampledParticles()
{
for (auto &particle : make_indexed_view(m_particles, m_sampledIndex)) {
// ...
}
}
当然,您必须执行基准测试以确定索引视图是否有利。此类基准测试必须使用真实数据并在发布版本中完成 - 对调试版本进行基准测试几乎无法了解发布版本的性能。