490 [[nodiscard]] std::uint64_t
version() const noexcept {
return version_; }
491 [[nodiscard]]
bool directed() const noexcept {
return directed_; }
494 const auto n =
static_cast<std::size_t
>(v) + 1;
504 bool saw_edge =
false;
505 std::vector<std::pair<VertexId, VertexId>> arcs;
506 arcs.reserve(directed_ ? edges.size() : edges.size() * 2);
507 for (
const auto& [u, v] : edges) {
508 if (u == v)
continue;
509 max_vertex = std::max(max_vertex, std::max(u, v));
511 arcs.emplace_back(u, v);
512 if (!directed_) arcs.emplace_back(v, u);
518 patches_out_.
clear();
526 dirty_out_rows_.clear();
527 dirty_in_rows_.clear();
544 for (
const auto& op : batch.
updates) apply_unversioned(op);
545 if (!batch.
empty()) {
547 automatic_storage_maintenance();
552 return materialize_row(base_out_, patches_out_, delta_out_, u);
556 return materialize_row(base_in_, patches_in_, delta_in_, v);
561 for_each_effective_row(base_out_, patches_out_, delta_out_, u, std::forward<Fn>(fn));
566 for_each_effective_row(base_in_, patches_in_, delta_in_, v, std::forward<Fn>(fn));
570 return delta_out_.
empty() && delta_in_.
empty();
574 if (
const auto* patch = patches_out_.
find(u))
return *patch;
575 return base_out_.
row(u);
579 if (
const auto* patch = patches_in_.
find(v))
return *patch;
580 return base_in_.
row(v);
585 if (
const auto overlay = delta_out_.
override_for(u, v); overlay.has_value())
return *overlay;
586 return compact_contains(base_out_, patches_out_, u, v);
594 return compact_out_edges_;
603 dirty_out_rows_.capacity() *
sizeof(
VertexId) +
604 dirty_in_rows_.capacity() *
sizeof(
VertexId);
608 return static_cast<double>(delta_out_.
size()) /
609 static_cast<double>(std::max<std::size_t>(1, compact_out_edges_));
613 return unique_dirty_count(dirty_out_rows_);
617 return unique_dirty_count(dirty_in_rows_);
621 bool compacted =
false;
622 compacted |= compact_dense_rows(base_out_, patches_out_, delta_out_, dirty_out_rows_,
623 compact_out_edges_, threshold);
624 compacted |= compact_dense_rows(base_in_, patches_in_, delta_in_, dirty_in_rows_,
625 compact_in_edges_, threshold);
635 compact_marked_rows(base_out_, patches_out_, delta_out_, dirty_out_rows_, compact_out_edges_);
636 compact_marked_rows(base_in_, patches_in_, delta_in_, dirty_in_rows_, compact_in_edges_);
647 if (patches.empty())
return base.row(u);
648 if (
const auto* patch = patches.find(u))
return *patch;
655 const auto row = compact_row(base, patches, u);
656 return std::binary_search(row.begin(), row.end(), v);
660 static void for_each_effective_row(
const storage_detail::SegmentedCsr& base,
661 const storage_detail::CompactRowPatches& patches,
662 const storage_detail::PackedDeltaStore& delta,
665 const auto base_row = compact_row(base, patches, u);
666 const auto overlay = delta.row(u);
669 while (i < base_row.size() || j < overlay.size()) {
670 if (j == overlay.size() || (i < base_row.size() && base_row[i] < overlay[j].dst)) {
672 }
else if (i == base_row.size() || overlay[j].dst < base_row[i]) {
673 if (overlay[j].present) fn(overlay[j].dst);
676 if (overlay[j].present) fn(base_row[i]);
683 static std::vector<VertexId> materialize_row(
const storage_detail::SegmentedCsr& base,
684 const storage_detail::CompactRowPatches& patches,
685 const storage_detail::PackedDeltaStore& delta,
687 const auto base_row = compact_row(base, patches, u);
688 const auto overlay = delta.row(u);
689 if (overlay.empty())
return {base_row.begin(), base_row.end()};
691 std::vector<VertexId> out;
692 out.reserve(base_row.size() + overlay.size());
695 while (i < base_row.size() || j < overlay.size()) {
696 if (j == overlay.size() || (i < base_row.size() && base_row[i] < overlay[j].dst)) {
697 out.push_back(base_row[i++]);
698 }
else if (i == base_row.size() || overlay[j].dst < base_row[i]) {
699 if (overlay[j].present) out.push_back(overlay[j].dst);
702 if (overlay[j].present) out.push_back(base_row[i]);
710 static std::size_t unique_dirty_count(
const std::vector<VertexId>& rows) {
711 if (rows.empty())
return 0;
712 std::vector<VertexId> copy = rows;
713 std::sort(copy.begin(), copy.end());
714 return static_cast<std::size_t
>(std::unique(copy.begin(), copy.end()) - copy.begin());
717 void apply_unversioned(
const EdgeUpdate& op) {
720 if (op.src == op.dst)
return;
722 apply_arc(op.src, op.dst, op.add);
723 if (!directed_) apply_arc(op.dst, op.src, op.add);
727 const auto base_present_out = compact_contains(base_out_, patches_out_, u, v);
728 if (!delta_out_.
set_if_changed(u, v, present, base_present_out))
return;
730 delta_in_.
set(v, u, present, base_present_out);
731 dirty_out_rows_.push_back(u);
732 dirty_in_rows_.push_back(v);
735 static void normalize_dirty(std::vector<VertexId>& rows) {
736 std::sort(rows.begin(), rows.end());
737 rows.erase(std::unique(rows.begin(), rows.end()), rows.end());
740 static void compact_one_row(
const storage_detail::SegmentedCsr& base,
741 storage_detail::CompactRowPatches& patches,
742 storage_detail::PackedDeltaStore& delta,
744 std::size_t& compact_edges) {
745 if (delta.row(u).empty())
return;
746 const auto old_size = compact_row(base, patches, u).size();
747 auto merged = materialize_row(base, patches, delta, u);
748 const auto new_size = merged.size();
749 patches.set(u, std::move(merged));
750 delta.clear_range(
static_cast<std::size_t
>(u),
static_cast<std::size_t
>(u) + 1);
751 compact_edges = compact_edges - old_size + new_size;
754 static void compact_marked_rows(
const storage_detail::SegmentedCsr& base,
755 storage_detail::CompactRowPatches& patches,
756 storage_detail::PackedDeltaStore& delta,
757 std::vector<VertexId>& dirty_rows,
758 std::size_t& compact_edges) {
759 normalize_dirty(dirty_rows);
760 for (
const auto u : dirty_rows) compact_one_row(base, patches, delta, u, compact_edges);
764 static bool compact_dense_rows(
const storage_detail::SegmentedCsr& base,
765 storage_detail::CompactRowPatches& patches,
766 storage_detail::PackedDeltaStore& delta,
767 std::vector<VertexId>& dirty_rows,
768 std::size_t& compact_edges,
770 normalize_dirty(dirty_rows);
771 bool compacted =
false;
772 std::vector<VertexId> pending;
773 pending.reserve(dirty_rows.size());
774 for (
const auto u : dirty_rows) {
775 const auto overlay_size = delta.row(u).size();
776 if (overlay_size == 0)
continue;
777 const auto base_size = compact_row(base, patches, u).size();
778 const auto work_budget = std::max<std::size_t>(8, base_size);
779 const double density =
static_cast<double>(overlay_size) /
static_cast<double>(work_budget);
780 if (density < threshold) {
781 pending.push_back(u);
784 compact_one_row(base, patches, delta, u, compact_edges);
787 dirty_rows.swap(pending);
791 void automatic_storage_maintenance() {
792 constexpr std::size_t kAutomaticMinimumDeltaEntries = 65536;
793 constexpr double kAutomaticGlobalDeltaRatio = 0.01;
794 constexpr double kRowDeltaDensityThreshold = 0.50;
795 constexpr double kFragmentationThreshold = 0.60;
796 if (delta_out_.
size() >= kAutomaticMinimumDeltaEntries &&
804 bool directed_{
false};
805 storage_detail::SegmentedCsr base_out_;
806 storage_detail::SegmentedCsr base_in_;
807 storage_detail::CompactRowPatches patches_out_;
808 storage_detail::CompactRowPatches patches_in_;
809 storage_detail::PackedDeltaStore delta_out_;
810 storage_detail::PackedDeltaStore delta_in_;
811 std::vector<VertexId> dirty_out_rows_;
812 std::vector<VertexId> dirty_in_rows_;
813 std::size_t compact_out_edges_{0};
814 std::size_t compact_in_edges_{0};
815 std::uint64_t version_{0};