Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions cpp/include/seqwin/graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <string>
#include <vector>

#include "seqwin/no_init_array.hpp"

namespace seqwin {

struct Kmer {
Expand All @@ -29,13 +31,11 @@ struct Edge {
};

struct Graph {
std::vector<Kmer> kmers;
std::vector<std::uint64_t> idx;
std::vector<Node> nodes;
std::vector<Edge> edges;
NoInitArray<Kmer> kmers;
NoInitArray<std::uint64_t> idx;
NoInitArray<Node> nodes;
NoInitArray<Edge> edges;
std::vector<std::vector<std::string>> ids_by_assembly;
std::size_t n_kmers = 0;
std::size_t start_assembly = 0;
};

Graph build(
Expand Down
12 changes: 11 additions & 1 deletion cpp/include/seqwin/helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,23 @@ namespace seqwin {
*/
class ThreadPool;

struct ThreadGraph {
std::vector<Kmer> kmers; // Needs push_back() support (exact kmer count is not known)
NoInitArray<std::uint64_t> idx;
NoInitArray<Node> nodes;
NoInitArray<Edge> edges;
std::vector<std::vector<std::string>> ids_by_assembly;
std::size_t n_kmers = 0;
std::size_t start_assembly = 0;
};

void log_python(
const std::string& message,
const std::string& level = "info"
);

Graph merge_thread_graphs(
std::vector<Graph>& graphs,
std::vector<ThreadGraph>& graphs,
std::size_t n_assemblies,
ThreadPool& pool
);
Expand Down
86 changes: 86 additions & 0 deletions cpp/include/seqwin/no_init_array.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#pragma once

#include <cstddef>
#include <memory>
#include <utility>

namespace seqwin {

/**
* A fixed-size owning array.
*
* Unlike `std::vector<T>(n) or std::make_unique<T[]>(n)`, this class allocates
* with `new T[n]`. For scalar and trivially default-initialized element types,
* this avoids value-initializing every element, which can be expensive for very
* large arrays.
*
* Important:
* - Every element must be assigned before it is read.
* - This is not a full `std::vector` replacement.
* - It intentionally provides no `resize()`, `reserve()`, `push_back()`, or `capacity()`.
*/
template <typename T>
class NoInitArray {
public:
NoInitArray() noexcept = default;

explicit NoInitArray(std::size_t size)
: size_(size)
, data_(size == 0 ? nullptr : new T[size])
{}

NoInitArray(const NoInitArray&) = delete;
NoInitArray& operator=(const NoInitArray&) = delete;

NoInitArray(NoInitArray&& other) noexcept
: size_(other.size_)
, data_(std::move(other.data_))
{
other.size_ = 0;
}

NoInitArray& operator=(NoInitArray&& other) noexcept
{
if (this != &other) {
data_ = std::move(other.data_);
size_ = other.size_;
other.size_ = 0;
}
return *this;
}

std::size_t size() const noexcept { return size_; }
bool empty() const noexcept { return size_ == 0; }

T* data() noexcept { return data_.get(); }
const T* data() const noexcept { return data_.get(); }
T* begin() noexcept { return data_.get(); }
T* end() noexcept { return data_.get() + size_; }
const T* begin() const noexcept { return data_.get(); }
const T* end() const noexcept { return data_.get() + size_; }
const T* cbegin() const noexcept { return data_.get(); }
const T* cend() const noexcept { return data_.get() + size_; }

T& operator[](std::size_t i) noexcept { return data_[i]; }
const T& operator[](std::size_t i) const noexcept { return data_[i]; }

void swap(NoInitArray& other) noexcept
{
std::swap(size_, other.size_);
std::swap(data_, other.data_);
}

friend void swap(NoInitArray& a, NoInitArray& b) noexcept { a.swap(b); }

void reset() noexcept
{
data_.reset();
size_ = 0;
}

private:
std::size_t size_ = 0;
std::unique_ptr<T[]> data_;
};

} // namespace seqwin
12 changes: 6 additions & 6 deletions cpp/src/seqwin/graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct EdgeState {
std::size_t last_seen_assembly = std::numeric_limits<std::size_t>::max();
};

Graph build_worker(
ThreadGraph build_worker(
const std::vector<std::string>& assembly_paths,
std::size_t kmerlen,
std::size_t windowsize,
Expand All @@ -61,7 +61,7 @@ Graph build_worker(
windowsize
);

Graph graph;
ThreadGraph graph;
graph.kmers.reserve(n_kmers_est);
graph.ids_by_assembly.resize(end_assembly - start_assembly);
graph.start_assembly = start_assembly;
Expand Down Expand Up @@ -144,14 +144,14 @@ Graph build_worker(
}

// Create edges first to reduce peak memory
graph.edges.resize(edge_map.size());
graph.edges = NoInitArray<Edge>(edge_map.size());
std::size_t edge_i = 0;
for (const auto& [key, state] : edge_map) {
graph.edges[edge_i++] = Edge{key.first, key.second, state.weight};
}
std::unordered_map<EdgeKey, EdgeState, EdgeKeyHash>().swap(edge_map);

graph.idx.resize(graph.n_kmers);
graph.idx = NoInitArray<std::uint64_t>(graph.n_kmers);
std::uint64_t cursor = 0;
for (auto& [hash, state] : node_map) {
(void)hash;
Expand All @@ -165,7 +165,7 @@ Graph build_worker(
graph.idx[node_it->second.cursor++] = static_cast<std::uint64_t>(i);
}

graph.nodes.resize(node_map.size());
graph.nodes = NoInitArray<Node>(node_map.size());
std::size_t node_i = 0;
for (const auto& [hash, state] : node_map) {
const auto stop = state.start + state.count;
Expand Down Expand Up @@ -210,7 +210,7 @@ Graph build(
}

ThreadPool pool(n_workers);
std::vector<Graph> graphs(n_workers);
std::vector<ThreadGraph> graphs(n_workers);

const std::size_t base = n_assemblies / n_workers;
const std::size_t rem = n_assemblies % n_workers;
Expand Down
Loading
Loading