Repository: vespa-engine/vespa
File: searchlib/src/vespa/searchlib/tensor/hnsw_index_loader.hpp (C++)

Code before:
void
HnswIndexLoader<ReaderType, type>::init()
{
    _entry_nodeid = next_int();
    _entry_level = next_int();
    _num_nodes = next_int();
}

template <typename ReaderType, HnswIndexType type>
HnswIndexLoader<ReaderType, type>::~HnswIndexLoader() = default;

template <typename ReaderType, HnswIndexType type>
HnswIndexLoader<ReaderType, type>::HnswIndexLoader(HnswGraph<type>& graph, IdMapping& id_mapping, std::unique_ptr<ReaderType> reader)
    : _graph(graph),
      _reader(std::move(reader)),
      _entry_nodeid(0),
      _entry_level(0),
      _num_nodes(0),
      _nodeid(0),
      _link_array(),
      _complete(false),
      _id_mapping(id_mapping)
{
    init();

    graph.nodes.ensure_size(_num_nodes);
}

template <typename ReaderType, HnswIndexType type>
bool
HnswIndexLoader<ReaderType, type>::load_next()
{
    assert(!_complete);
    static constexpr bool identity_mapping = (type == HnswIndexType::SINGLE);
    if (_nodeid < _num_nodes) {
        uint32_t num_levels = next_int();
        if (num_levels > 0) {
            uint32_t docid = identity_mapping ? _nodeid : next_int();
            uint32_t subspace = identity_mapping  ? 0 : next_int();
            _graph.make_node(_nodeid, docid, subspace, num_levels);
            for (uint32_t level = 0; level < num_levels; ++level) {
                uint32_t num_links = next_int();
                _link_array.resize(num_links);
                if (num_links != 0) {
                    next_N_ints(_link_array.data(), num_links);
                }
                _graph.set_link_array(_nodeid, level, _link_array);
            }
        }
    }
    if (++_nodeid < _num_nodes) {

Review comment (performance):
Is there any difference in performance between doing `ensure_size(_num_nodes)` and `reserve(_num_nodes)`? The former will also do an internal O(n) `push_back` loop in the RCU vector to default-init the graph nodes (which will bring all of the backing array buffer memory in and out of the caches), the latter will defer an expected single `push_back` to `_graph.make_node` (which calls `ensure_size` with the current node ID + 1.

If there's no performance benefit to doing `ensure_size` here, I think I'm leaning towards _semantically_ preferring `reserve` instead since the graph nodes are technically not yet valid at this point (but we do want the memory preallocated). But I'm open for other arguments.

Code after:
void
HnswIndexLoader<ReaderType, type>::init()
{
    _entry_nodeid = next_int();
    _entry_level = next_int();
    _num_nodes = next_int();
}

template <typename ReaderType, HnswIndexType type>
HnswIndexLoader<ReaderType, type>::~HnswIndexLoader() = default;

template <typename ReaderType, HnswIndexType type>
HnswIndexLoader<ReaderType, type>::HnswIndexLoader(HnswGraph<type>& graph, IdMapping& id_mapping, std::unique_ptr<ReaderType> reader)
    : _graph(graph),
      _reader(std::move(reader)),
      _entry_nodeid(0),
      _entry_level(0),
      _num_nodes(0),
      _nodeid(0),
      _link_array(),
      _complete(false),
      _id_mapping(id_mapping)
{
    init();

    graph.nodes.reserve(_num_nodes);
}

template <typename ReaderType, HnswIndexType type>
bool
HnswIndexLoader<ReaderType, type>::load_next()
{
    assert(!_complete);
    static constexpr bool identity_mapping = (type == HnswIndexType::SINGLE);
    if (_nodeid < _num_nodes) {
        uint32_t num_levels = next_int();
        if (num_levels > 0) {
            uint32_t docid = identity_mapping ? _nodeid : next_int();
            uint32_t subspace = identity_mapping  ? 0 : next_int();
            _graph.make_node(_nodeid, docid, subspace, num_levels);
            for (uint32_t level = 0; level < num_levels; ++level) {
                uint32_t num_links = next_int();
                _link_array.resize(num_links);
                if (num_links != 0) {
                    next_N_ints(_link_array.data(), num_links);
                }
                _graph.set_link_array(_nodeid, level, _link_array);
            }
        }
    }
    if (++_nodeid < _num_nodes) {