Add MANIFOLD_TIMING build option + perfPhases benchmark (#1670)
* Add MANIFOLD_TIMING build option for release-build phase timing MANIFOLD_DEBUG enables the Timer instrumentation in Boolean3 but also the DEBUG_ASSERTs in hot kernels (Interpolate, Intersect, Kernel11), inflating measurements. MANIFOLD_TIMING gates just the Timer code. Also splits the Intersections ctor segment into four sub-phases (Intersect12 P->Q, Intersect12 Q->P, Winding03 P, Winding03 Q) so the verbose output maps 1:1 to the four ctor cancel checkpoints in #1669. MANIFOLD_DEBUG is a strict superset, so existing builds get these additional sub-phase lines alongside the Intersections total. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add perfPhases benchmark Reusable harness for MANIFOLD_TIMING=ON (or MANIFOLD_DEBUG=ON). Runs Add/Subtract/Intersect on sphere (8 scales) and MengerSponge(3,4), forcing sponge construction before timing the ops. Gated on MANIFOLD_DEBUG OR MANIFOLD_TIMING (matches minimizeTestcase pattern). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
adaf64c4a7
commit
8d28267ca1
@@ -40,6 +40,7 @@ set(
|
||||
option(MANIFOLD_CROSS_SECTION "Build CrossSection for 2D support" ON)
|
||||
option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF)
|
||||
option(MANIFOLD_ASSERT "Enable assertions - requires MANIFOLD_DEBUG" OFF)
|
||||
option(MANIFOLD_TIMING "Enable Boolean3 phase timing without debug overhead" OFF)
|
||||
option(MANIFOLD_STRICT "Treat compile warnings as fatal build errors" OFF)
|
||||
option(
|
||||
MANIFOLD_DOWNLOADS
|
||||
|
||||
@@ -21,6 +21,16 @@ target_link_libraries(perfTest manifold)
|
||||
target_compile_options(perfTest PRIVATE ${MANIFOLD_FLAGS})
|
||||
exportbin(perfTest)
|
||||
|
||||
if(MANIFOLD_DEBUG OR MANIFOLD_TIMING)
|
||||
add_executable(perfPhases perf_phases.cpp)
|
||||
target_link_libraries(perfPhases manifold samples)
|
||||
target_include_directories(
|
||||
perfPhases PRIVATE ${PROJECT_SOURCE_DIR}/samples/include
|
||||
)
|
||||
target_compile_options(perfPhases PRIVATE ${MANIFOLD_FLAGS})
|
||||
exportbin(perfPhases)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_PAR AND NOT MSVC)
|
||||
add_executable(stlTest stl_test.cpp)
|
||||
target_link_libraries(stlTest PRIVATE manifold TBB::tbb)
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2026 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Prints per-phase Boolean3 wall-clock timings on representative workloads.
|
||||
// Requires MANIFOLD_TIMING=ON (or MANIFOLD_DEBUG=ON) so Timer::Print fires.
|
||||
// Used for cancel-latency analysis and for identifying dominant phases.
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "manifold/manifold.h"
|
||||
#include "samples.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace {
|
||||
// Runs one boolean op of each kind (Add, Subtract, Intersect) against the
|
||||
// same input pair. Each is a single boolean so Timer output is clean.
|
||||
void BenchAll(const std::string& workload, const Manifold& a,
|
||||
const Manifold& b) {
|
||||
struct Op {
|
||||
const char* name;
|
||||
Manifold (*apply)(const Manifold&, const Manifold&);
|
||||
};
|
||||
const Op ops[] = {
|
||||
{"add", [](const Manifold& x, const Manifold& y) { return x + y; }},
|
||||
{"subtract", [](const Manifold& x, const Manifold& y) { return x - y; }},
|
||||
{"intersect", [](const Manifold& x, const Manifold& y) { return x ^ y; }},
|
||||
};
|
||||
for (const auto& op : ops) {
|
||||
std::cout << "=== " << workload << ": " << op.name
|
||||
<< ", nTri(a) = " << a.NumTri() << " ===" << std::endl;
|
||||
auto t0 = std::chrono::high_resolution_clock::now();
|
||||
Manifold result = op.apply(a, b);
|
||||
result.NumTri();
|
||||
auto t1 = std::chrono::high_resolution_clock::now();
|
||||
std::cout << "total = " << std::chrono::duration<double>(t1 - t0).count()
|
||||
<< " sec\n"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ManifoldParams().verbose = 2;
|
||||
|
||||
// Sphere at escalating scales — uniform geometry, single boolean per op
|
||||
// so Timer output is readable.
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
const int segments = (8 << i) * 4;
|
||||
Manifold sphere = Manifold::Sphere(1, segments);
|
||||
Manifold sphere2 = sphere.Translate(vec3(0.5));
|
||||
BenchAll("Sphere(" + std::to_string(segments) + ")", sphere, sphere2);
|
||||
}
|
||||
|
||||
// Triangulator-heavy: Menger sponge. Forcing sponge.NumTri() first
|
||||
// separates the sponge's internal CSG construction from the measured
|
||||
// ops, so `total` reflects just each boolean.
|
||||
for (int level : {3, 4}) {
|
||||
Manifold sponge = MengerSponge(level);
|
||||
Manifold sponge2 = sponge.Translate(vec3(0.3));
|
||||
sponge.NumTri(); // force construction before timing the ops
|
||||
BenchAll("MengerSponge(" + std::to_string(level) + ")", sponge, sponge2);
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,9 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#include "./math.h"
|
||||
@@ -666,6 +667,9 @@ void Diff(const std::vector<T>& a, const std::vector<T>& b) {
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
struct Timer {
|
||||
std::chrono::high_resolution_clock::time_point start, end;
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ set(
|
||||
OPTIONS
|
||||
MANIFOLD_DEBUG
|
||||
MANIFOLD_ASSERT
|
||||
MANIFOLD_TIMING
|
||||
MANIFOLD_CROSS_SECTION
|
||||
TRACY_ENABLE
|
||||
TRACY_MEMORY_USAGE
|
||||
|
||||
+24
-3
@@ -498,9 +498,11 @@ Boolean3::Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ,
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
Timer intersections;
|
||||
intersections.Start();
|
||||
Timer intersect12P, intersect12Q, winding03P, winding03Q;
|
||||
intersect12P.Start();
|
||||
#endif
|
||||
|
||||
// Level 3
|
||||
@@ -508,7 +510,14 @@ Boolean3::Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ,
|
||||
// that intersect, and record the direction the edge is passing through the
|
||||
// triangle.
|
||||
xv12_ = Intersect12<true>(inP, inQ, expandP_);
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
intersect12P.Stop();
|
||||
intersect12Q.Start();
|
||||
#endif
|
||||
xv21_ = Intersect12<false>(inP, inQ, expandP_);
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
intersect12Q.Stop();
|
||||
#endif
|
||||
|
||||
if (xv12_.x12.size() > INT_MAX_SZ || xv21_.x12.size() > INT_MAX_SZ) {
|
||||
valid = false;
|
||||
@@ -517,14 +526,26 @@ Boolean3::Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ,
|
||||
|
||||
// Compute winding numbers of all vertices using flood fill
|
||||
// Vertices on the same connected component have the same winding number
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
winding03P.Start();
|
||||
#endif
|
||||
w03_ = Winding03<true>(inP, inQ, xv12_.p1q2, expandP_);
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
winding03P.Stop();
|
||||
winding03Q.Start();
|
||||
#endif
|
||||
w30_ = Winding03<false>(inP, inQ, xv21_.p1q2, expandP_);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
winding03Q.Stop();
|
||||
intersections.Stop();
|
||||
|
||||
if (ManifoldParams().verbose >= 2) {
|
||||
intersections.Print("Intersections");
|
||||
intersect12P.Print(" Intersect12 P->Q");
|
||||
intersect12Q.Print(" Intersect12 Q->P");
|
||||
winding03P.Print(" Winding03 P");
|
||||
winding03Q.Print(" Winding03 Q");
|
||||
intersections.Print("Intersections (total)");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -651,7 +651,7 @@ namespace manifold {
|
||||
|
||||
Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
ZoneScoped;
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
Timer assemble;
|
||||
assemble.Start();
|
||||
#endif
|
||||
@@ -825,7 +825,7 @@ Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
vP2R.clear();
|
||||
vQ2R.clear();
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
assemble.Stop();
|
||||
Timer triangulate;
|
||||
triangulate.Start();
|
||||
@@ -838,7 +838,7 @@ Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
|
||||
outR.ReorderHalfedges();
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
triangulate.Stop();
|
||||
Timer simplify;
|
||||
simplify.Start();
|
||||
@@ -859,7 +859,7 @@ Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
DEBUG_ASSERT(outR.Is2Manifold(), logicErr,
|
||||
"simplified mesh is not 2-manifold!");
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
simplify.Stop();
|
||||
Timer sort;
|
||||
sort.Start();
|
||||
@@ -869,7 +869,7 @@ Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
outR.SortGeometry();
|
||||
outR.IncrementMeshIDs();
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#if defined(MANIFOLD_DEBUG) || defined(MANIFOLD_TIMING)
|
||||
sort.Stop();
|
||||
if (ManifoldParams().verbose >= 2) {
|
||||
assemble.Print("Assembly");
|
||||
|
||||
Reference in New Issue
Block a user