Skip to content

Add tracing and event support for parallel tasks#9192

Open
alexreinking wants to merge 5 commits into
mainfrom
alexreinking/trace-thread-events
Open

Add tracing and event support for parallel tasks#9192
alexreinking wants to merge 5 commits into
mainfrom
alexreinking/trace-thread-events

Conversation

@alexreinking

Copy link
Copy Markdown
Member

This pull request adds tracing and event support for tasks that execute in parallel. It also optimizes the trace packet format by compressing it through the union of lifetime-disjoint fields. Combining these changes in a single pull request avoids unnecessary API and ABI churn.

Breaking changes

None.

Checklist

  • Tests added or updated (not required for docs, CI config, or typo fixes)
  • Documentation updated (if public API changed)
  • Python bindings updated (if public API changed)
  • Benchmarks are included here if the change is intended to affect performance.
  • Commits include AI attribution where applicable (see Code of Conduct)

Also compresses the trace packet format by union-ing
lifetime-disjoint fields. It was decided to do this
here rather than in a separate PR to avoid excess API
and ABI churn.

Co-authored-by: OpenAI Codex <noreply@openai.com>
@alexreinking alexreinking added the release_notes For changes that may warrant a note in README for official releases. label Jul 6, 2026
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.24752% with 25 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@3cf47df). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/LLVM_Runtime_Linker.cpp 71.05% 9 Missing and 2 partials ⚠️
src/runtime/HalideRuntime.h 0.00% 7 Missing ⚠️
src/WasmExecutor.cpp 54.54% 4 Missing and 1 partial ⚠️
src/VectorizeLoops.cpp 60.00% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #9192   +/-   ##
=======================================
  Coverage        ?   69.39%           
=======================================
  Files           ?      254           
  Lines           ?    78345           
  Branches        ?    18739           
=======================================
  Hits            ?    54369           
  Misses          ?    18486           
  Partials        ?     5490           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

};

#if (__cplusplus >= 201103L || _MSVC_LANG >= 201103L)
static_assert(sizeof(halide_trace_packet_t) == 6 * sizeof(uint32_t), "size mismatch in halide_trace_packet_t");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So just to confirm, the packet size savings from this change is 4B/packet (28B → 24B) due to the type punning trick with the union above, correct? And then store/load events will access value_index and type in these offsets, while all other events will access id and thread_id, yes? Makes sense, just trying to confirm my understanding.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's all right

Comment thread src/runtime/tracing.cpp
Comment on lines +211 to +217
if (e->event == halide_trace_load || e->event == halide_trace_store) {
packet->value_index = e->value_index;
packet->type = e->type;
} else {
packet->id = my_id;
packet->thread_id = (e->event == halide_trace_begin_parallel_task) ? e->thread_id : 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, this answers my question from above, looks like I'm understanding the access pattern correctly!

@parkerziegler parkerziegler left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I can't comment too much on the C++ side of things, but from a Halidoscope perspective the changes should be reasonable to port. We'll need to:

  1. Adjust the Rust packet parsing to account for the union type punning and changed packet size. The Rust parser will handle this by just selecting the proper decoding function based on the event type (i.e., should I interpret the next four bytes as, say, an i32 for thread_id or a sequence of u8, u8, u16 for type.code, type.bits, type.lane).
  2. Add the new event types.
  3. Ignore names on parallel events, which I believe correspond to "loop names" rather than func names based on L130-134 of test/correctness/tracing_thread_ids.cpp.

All in all, it should be pretty minimal. What I'll do is capture a trace with these changes locally, switch back over to my branch, and then implement the above updates on the Rust side. But I don't think that work needs to block this; I can always adjust my side if there's something I'm missing.

@alexreinking

alexreinking commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

Per discussion with @abadams, the reported thread-ids match the OS platform libraries. This resulted in a bunch more code since every platform does it a little differently, but I think this is a good net change.

We had discussed using pthread_self. This is actually not appropriate since it returns a pointer into pthread's structures representing the thread. It will identify the thread uniquely to the pthread system while it is alive, but can be reused later in the same process.

Experiment: pthread_self vs. pthread_threadid_np

Here is a simple program to spawn a few threads and probe the difference between the two calls.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdint.h>

void* thread_func(void* arg) {
    long thread_num = (long)arg;
    pthread_t self = pthread_self();
    
    uint64_t tid = 0;
    int ret = pthread_threadid_np(NULL, &tid);
    
    uint64_t tid_self = 0;
    int ret_self = pthread_threadid_np(self, &tid_self);
    
    printf("Thread %ld:\n", thread_num);
    printf("  pthread_self():            %p\n", (void*)self);
    if (ret == 0) {
        printf("  pthread_threadid_np(NULL): %llu (0x%llx)\n", (unsigned long long)tid, (unsigned long long)tid);
    } else {
        printf("  pthread_threadid_np(NULL) failed with %d\n", ret);
    }
    if (ret_self == 0) {
        printf("  pthread_threadid_np(self): %llu (0x%llx)\n", (unsigned long long)tid_self, (unsigned long long)tid_self);
    } else {
        printf("  pthread_threadid_np(self) failed with %d\n", ret_self);
    }
    printf("\n");
    
    return NULL;
}

int main() {
    pthread_t main_thread = pthread_self();
    uint64_t main_tid = 0;
    pthread_threadid_np(NULL, &main_tid);
    printf("Main Thread:\n");
    printf("  pthread_self():            %p\n", (void*)main_thread);
    printf("  pthread_threadid_np(NULL): %llu (0x%llx)\n\n", (unsigned long long)main_tid, (unsigned long long)main_tid);

#define NUM_THREADS 3
    pthread_t threads[NUM_THREADS];
    
    for (long i = 0; i < NUM_THREADS; i++) {
        if (pthread_create(&threads[i], NULL, thread_func, (void*)i) != 0) {
            perror("Failed to create thread");
            return 1;
        }
    }
    
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    
    return 0;
}

Output:

% ./pthread_test        
Main Thread:
  pthread_self():            0x1f9db1e80
  pthread_threadid_np(NULL): 9072032 (0x8a6da0)

Thread 0:
  pthread_self():            0x16b617000
  pthread_threadid_np(NULL): 9072033 (0x8a6da1)
  pthread_threadid_np(self): 9072033 (0x8a6da1)

Thread 2:
  pthread_self():            0x16b72f000
  pthread_threadid_np(NULL): 9072035 (0x8a6da3)
  pthread_threadid_np(self): 9072035 (0x8a6da3)

Thread 1:
  pthread_self():            0x16b6a3000
  pthread_threadid_np(NULL): 9072034 (0x8a6da2)
  pthread_threadid_np(self): 9072034 (0x8a6da2)

As can be seen, pthread_threadid_np returns a monotonically increasing counter, whereas pthread_self returns an unstructured pointer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release_notes For changes that may warrant a note in README for official releases.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants