Loading examples/gfind/sfind.cpp +95 −57 Changes for examples/gfind/sfind.cpp: 95 added lines, 57 removed lines. Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include <vector> #include <cstdlib> // For getenv #include <pthread.h> // Include Pthreads header #include <atomic> // For atomic counters using namespace std; Loading Loading @@ -63,14 +64,19 @@ typedef struct { static pfind_runtime_options_t runtime; static pfind_options_t* opt; // Global rank/size for logging and work distribution int sfind_rank = 0; int sfind_size = 1; [[noreturn]] void pfind_abort(const string& str) { cerr << "ERROR: " << str << endl; cerr << "ERROR [Rank " << sfind_rank << "]: " << str << endl; exit(1); } // (pfind_print_help and pfind_parse_args are identical to your provided code) #pragma region parsing // ... Paste your pfind_print_help and pfind_parse_args functions here ... static void pfind_print_help(const pfind_options_t* res) { printf("pfind \nSynopsis:\n" Loading @@ -93,7 +99,6 @@ pfind_parse_args(int argc, char** argv, bool force_print_help) { auto res = new pfind_options_t(); bool print_help = force_print_help; vector<char*> modified_argv(argv, argv + argc); for(int i = 1; i < argc; ++i) { if(strcmp(argv[i], "-newer") == 0 && i + 1 < argc) { res->timestamp_file = argv[i + 1]; Loading Loading @@ -184,6 +189,7 @@ pfind_parse_args(int argc, char** argv, bool force_print_help) { } } if(print_help) { if(sfind_rank == 0) pfind_print_help(res); exit(0); } Loading @@ -197,28 +203,27 @@ pfind_parse_args(int argc, char** argv, bool force_print_help) { } #pragma endregion // Structure to pass data to and from each worker thread // Structure to pass data to each worker thread // Using atomics for results is simpler than passing a pointer and locking struct ThreadData { int thread_id; const pfind_options_t* opt; const string* workdir; queue<int>* server_queue; pthread_mutex_t* queue_mutex; // Thread-local results to avoid race conditions unsigned long long local_found = 0; unsigned long long local_checked = 0; atomic<unsigned long long>* total_found; atomic<unsigned long long>* total_checked; }; // The function each worker thread will execute void* worker_routine(void* arg) { ThreadData* data = static_cast<ThreadData*>(arg); unsigned long long local_found = 0; unsigned long long local_checked = 0; while(true) { int server_id = -1; // Safely get a server ID from the shared queue pthread_mutex_lock(data->queue_mutex); if(!data->server_queue->empty()) { server_id = data->server_queue->front(); Loading @@ -226,35 +231,33 @@ worker_routine(void* arg) { } pthread_mutex_unlock(data->queue_mutex); // If the queue was empty, our work is done if(server_id == -1) { if(server_id == -1) break; } struct dirent_extended* entries = nullptr; long n = gkfs_getsingleserverdir(data->workdir->c_str(), &entries, server_id); if(n <= 0) { // Handle error or empty directory from server if(n <= 0) { if(n < 0) { cerr << "Warning: Thread " << data->thread_id << " received error from server " << server_id << endl; cerr << "Warning: Rank " << sfind_rank << " Thread " << data->thread_id << " received error from server " << server_id << endl; } if(entries) free(entries); // Ensure we free even on error if allocated free(entries); continue; } // Iterate through the results from this server char* ptr = reinterpret_cast<char*>(entries); int bytes_processed = 0; while(bytes_processed < n) { struct dirent_extended* temp = reinterpret_cast<struct dirent_extended*>(ptr); if(temp->d_reclen == 0) break; // Safety break break; if(temp->d_type != 1) { // Process files, not directories if(temp->d_type != 1) { bool timeOK = data->opt->timestamp_file.empty() || ((uint64_t) temp->ctime >= runtime.ctime_min); bool sizeOK = (data->opt->size == Loading @@ -263,25 +266,27 @@ worker_routine(void* arg) { bool nameOK = data->opt->name_pattern.empty() || regex_search(temp->d_name, data->opt->name_regex); if(timeOK && sizeOK && nameOK) { data->local_found++; } data->local_checked++; if(timeOK && sizeOK && nameOK) local_found++; local_checked++; } bytes_processed += temp->d_reclen; ptr += temp->d_reclen; } free(entries); // Free the memory allocated by gkfs_getsingleserverdir free(entries); } // Atomically add local results to the global counters data->total_found->fetch_add(local_found); data->total_checked->fetch_add(local_checked); return nullptr; } int process_parallel_pthreads(const pfind_options_t* opt) { unsigned long long global_found = 0; unsigned long long global_checked = 0; atomic<unsigned long long> global_found(0); atomic<unsigned long long> global_checked(0); runtime = {}; if(!opt->timestamp_file.empty()) { Loading @@ -300,65 +305,98 @@ process_parallel_pthreads(const pfind_options_t* opt) { if(workdir.empty()) workdir = "/"; // --- 1. Calculate this process's subset of servers --- int servers_per_proc = opt->num_servers / sfind_size; int remainder = opt->num_servers % sfind_size; int start_server = sfind_rank * servers_per_proc + min(sfind_rank, remainder); int end_server = start_server + servers_per_proc + (sfind_rank < remainder ? 1 : 0); int num_servers_for_this_rank = end_server - start_server; if(num_servers_for_this_rank <= 0) { cout << "[Rank " << sfind_rank << "] No servers to process. Exiting." << endl; return 0; } // --- 2. Populate the local work queue with this rank's servers --- queue<int> server_queue; for(int i = start_server; i < end_server; ++i) { server_queue.push(i); } // --- Pthreads Setup --- int num_threads = 20; // Default number of parallel requests const char* env_threads = getenv("GFIND_NUM_THREADS"); const char* env_threads = getenv("SFIND_NUM_THREADS"); if(env_threads) { try { num_threads = stoi(env_threads); } catch(...) { /* ignore, use default */ } catch(...) { /* ignore */ } } // Don't create more threads than there are servers to query num_threads = min(num_threads, opt->num_servers); num_threads = min(num_threads, num_servers_for_this_rank); cout << "Using " << num_threads << " threads to query " << opt->num_servers << " servers." << endl; cout << "[Rank " << sfind_rank << "] Processing servers " << start_server << "-" << end_server - 1 << " using " << num_threads << " threads." << endl; // 1. Create the shared work queue queue<int> server_queue; for(int i = 0; i < opt->num_servers; ++i) { server_queue.push(i); } // 2. Initialize the mutex pthread_mutex_t queue_mutex; pthread_mutex_init(&queue_mutex, nullptr); // 3. Create thread data structures vector<pthread_t> threads(num_threads); vector<ThreadData> thread_args(num_threads); // 4. Launch worker threads for(int i = 0; i < num_threads; ++i) { thread_args[i] = {.thread_id = i, .opt = opt, .workdir = &workdir, .server_queue = &server_queue, .queue_mutex = &queue_mutex}; thread_args[i] = {i, opt, &workdir, &server_queue, &queue_mutex, &global_found, &global_checked}; pthread_create(&threads[i], nullptr, worker_routine, &thread_args[i]); } // 5. Wait for all threads to complete for(int i = 0; i < num_threads; ++i) { pthread_join(threads[i], nullptr); } // 6. Aggregate results from all threads for(int i = 0; i < num_threads; ++i) { global_found += thread_args[i].local_found; global_checked += thread_args[i].local_checked; } // 7. Clean up pthread_mutex_destroy(&queue_mutex); // Results are already aggregated via atomics. // Each process prints its own sub-total. The final aggregation must be done // externally (e.g., with a script). cout << "MATCHED " << global_found << "/" << global_checked << endl; return 0; } int main(int argc, char** argv) { // --- Get Rank/Size from SLURM Environment Variables --- const char* env_rank = getenv("SLURM_PROCID"); const char* env_size = getenv("SLURM_NPROCS"); if(env_rank && env_size) { try { sfind_rank = stoi(env_rank); sfind_size = stoi(env_size); } catch(const std::exception& e) { cerr << "Could not parse SLURM environment variables: " << e.what() << ". Falling back to sequential mode." << endl; sfind_rank = 0; sfind_size = 1; } } else { if(getenv("SLURM_JOB_ID")) { cerr << "Warning: SLURM_JOB_ID is set, but SLURM_PROCID/SLURM_NPROCS are not. Are you running with `srun`?" << endl; } cout << "SLURM variables not found. Running in sequential mode (rank 0 of 1)." << endl; sfind_rank = 0; sfind_size = 1; } // Each process parses its own arguments. This is simple and correct. opt = pfind_parse_args(argc, argv, false); if(gkfs_getsingleserverdir == nullptr) { Loading Loading
examples/gfind/sfind.cpp +95 −57 Changes for examples/gfind/sfind.cpp: 95 added lines, 57 removed lines. Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include <vector> #include <cstdlib> // For getenv #include <pthread.h> // Include Pthreads header #include <atomic> // For atomic counters using namespace std; Loading Loading @@ -63,14 +64,19 @@ typedef struct { static pfind_runtime_options_t runtime; static pfind_options_t* opt; // Global rank/size for logging and work distribution int sfind_rank = 0; int sfind_size = 1; [[noreturn]] void pfind_abort(const string& str) { cerr << "ERROR: " << str << endl; cerr << "ERROR [Rank " << sfind_rank << "]: " << str << endl; exit(1); } // (pfind_print_help and pfind_parse_args are identical to your provided code) #pragma region parsing // ... Paste your pfind_print_help and pfind_parse_args functions here ... static void pfind_print_help(const pfind_options_t* res) { printf("pfind \nSynopsis:\n" Loading @@ -93,7 +99,6 @@ pfind_parse_args(int argc, char** argv, bool force_print_help) { auto res = new pfind_options_t(); bool print_help = force_print_help; vector<char*> modified_argv(argv, argv + argc); for(int i = 1; i < argc; ++i) { if(strcmp(argv[i], "-newer") == 0 && i + 1 < argc) { res->timestamp_file = argv[i + 1]; Loading Loading @@ -184,6 +189,7 @@ pfind_parse_args(int argc, char** argv, bool force_print_help) { } } if(print_help) { if(sfind_rank == 0) pfind_print_help(res); exit(0); } Loading @@ -197,28 +203,27 @@ pfind_parse_args(int argc, char** argv, bool force_print_help) { } #pragma endregion // Structure to pass data to and from each worker thread // Structure to pass data to each worker thread // Using atomics for results is simpler than passing a pointer and locking struct ThreadData { int thread_id; const pfind_options_t* opt; const string* workdir; queue<int>* server_queue; pthread_mutex_t* queue_mutex; // Thread-local results to avoid race conditions unsigned long long local_found = 0; unsigned long long local_checked = 0; atomic<unsigned long long>* total_found; atomic<unsigned long long>* total_checked; }; // The function each worker thread will execute void* worker_routine(void* arg) { ThreadData* data = static_cast<ThreadData*>(arg); unsigned long long local_found = 0; unsigned long long local_checked = 0; while(true) { int server_id = -1; // Safely get a server ID from the shared queue pthread_mutex_lock(data->queue_mutex); if(!data->server_queue->empty()) { server_id = data->server_queue->front(); Loading @@ -226,35 +231,33 @@ worker_routine(void* arg) { } pthread_mutex_unlock(data->queue_mutex); // If the queue was empty, our work is done if(server_id == -1) { if(server_id == -1) break; } struct dirent_extended* entries = nullptr; long n = gkfs_getsingleserverdir(data->workdir->c_str(), &entries, server_id); if(n <= 0) { // Handle error or empty directory from server if(n <= 0) { if(n < 0) { cerr << "Warning: Thread " << data->thread_id << " received error from server " << server_id << endl; cerr << "Warning: Rank " << sfind_rank << " Thread " << data->thread_id << " received error from server " << server_id << endl; } if(entries) free(entries); // Ensure we free even on error if allocated free(entries); continue; } // Iterate through the results from this server char* ptr = reinterpret_cast<char*>(entries); int bytes_processed = 0; while(bytes_processed < n) { struct dirent_extended* temp = reinterpret_cast<struct dirent_extended*>(ptr); if(temp->d_reclen == 0) break; // Safety break break; if(temp->d_type != 1) { // Process files, not directories if(temp->d_type != 1) { bool timeOK = data->opt->timestamp_file.empty() || ((uint64_t) temp->ctime >= runtime.ctime_min); bool sizeOK = (data->opt->size == Loading @@ -263,25 +266,27 @@ worker_routine(void* arg) { bool nameOK = data->opt->name_pattern.empty() || regex_search(temp->d_name, data->opt->name_regex); if(timeOK && sizeOK && nameOK) { data->local_found++; } data->local_checked++; if(timeOK && sizeOK && nameOK) local_found++; local_checked++; } bytes_processed += temp->d_reclen; ptr += temp->d_reclen; } free(entries); // Free the memory allocated by gkfs_getsingleserverdir free(entries); } // Atomically add local results to the global counters data->total_found->fetch_add(local_found); data->total_checked->fetch_add(local_checked); return nullptr; } int process_parallel_pthreads(const pfind_options_t* opt) { unsigned long long global_found = 0; unsigned long long global_checked = 0; atomic<unsigned long long> global_found(0); atomic<unsigned long long> global_checked(0); runtime = {}; if(!opt->timestamp_file.empty()) { Loading @@ -300,65 +305,98 @@ process_parallel_pthreads(const pfind_options_t* opt) { if(workdir.empty()) workdir = "/"; // --- 1. Calculate this process's subset of servers --- int servers_per_proc = opt->num_servers / sfind_size; int remainder = opt->num_servers % sfind_size; int start_server = sfind_rank * servers_per_proc + min(sfind_rank, remainder); int end_server = start_server + servers_per_proc + (sfind_rank < remainder ? 1 : 0); int num_servers_for_this_rank = end_server - start_server; if(num_servers_for_this_rank <= 0) { cout << "[Rank " << sfind_rank << "] No servers to process. Exiting." << endl; return 0; } // --- 2. Populate the local work queue with this rank's servers --- queue<int> server_queue; for(int i = start_server; i < end_server; ++i) { server_queue.push(i); } // --- Pthreads Setup --- int num_threads = 20; // Default number of parallel requests const char* env_threads = getenv("GFIND_NUM_THREADS"); const char* env_threads = getenv("SFIND_NUM_THREADS"); if(env_threads) { try { num_threads = stoi(env_threads); } catch(...) { /* ignore, use default */ } catch(...) { /* ignore */ } } // Don't create more threads than there are servers to query num_threads = min(num_threads, opt->num_servers); num_threads = min(num_threads, num_servers_for_this_rank); cout << "Using " << num_threads << " threads to query " << opt->num_servers << " servers." << endl; cout << "[Rank " << sfind_rank << "] Processing servers " << start_server << "-" << end_server - 1 << " using " << num_threads << " threads." << endl; // 1. Create the shared work queue queue<int> server_queue; for(int i = 0; i < opt->num_servers; ++i) { server_queue.push(i); } // 2. Initialize the mutex pthread_mutex_t queue_mutex; pthread_mutex_init(&queue_mutex, nullptr); // 3. Create thread data structures vector<pthread_t> threads(num_threads); vector<ThreadData> thread_args(num_threads); // 4. Launch worker threads for(int i = 0; i < num_threads; ++i) { thread_args[i] = {.thread_id = i, .opt = opt, .workdir = &workdir, .server_queue = &server_queue, .queue_mutex = &queue_mutex}; thread_args[i] = {i, opt, &workdir, &server_queue, &queue_mutex, &global_found, &global_checked}; pthread_create(&threads[i], nullptr, worker_routine, &thread_args[i]); } // 5. Wait for all threads to complete for(int i = 0; i < num_threads; ++i) { pthread_join(threads[i], nullptr); } // 6. Aggregate results from all threads for(int i = 0; i < num_threads; ++i) { global_found += thread_args[i].local_found; global_checked += thread_args[i].local_checked; } // 7. Clean up pthread_mutex_destroy(&queue_mutex); // Results are already aggregated via atomics. // Each process prints its own sub-total. The final aggregation must be done // externally (e.g., with a script). cout << "MATCHED " << global_found << "/" << global_checked << endl; return 0; } int main(int argc, char** argv) { // --- Get Rank/Size from SLURM Environment Variables --- const char* env_rank = getenv("SLURM_PROCID"); const char* env_size = getenv("SLURM_NPROCS"); if(env_rank && env_size) { try { sfind_rank = stoi(env_rank); sfind_size = stoi(env_size); } catch(const std::exception& e) { cerr << "Could not parse SLURM environment variables: " << e.what() << ". Falling back to sequential mode." << endl; sfind_rank = 0; sfind_size = 1; } } else { if(getenv("SLURM_JOB_ID")) { cerr << "Warning: SLURM_JOB_ID is set, but SLURM_PROCID/SLURM_NPROCS are not. Are you running with `srun`?" << endl; } cout << "SLURM variables not found. Running in sequential mode (rank 0 of 1)." << endl; sfind_rank = 0; sfind_size = 1; } // Each process parses its own arguments. This is simple and correct. opt = pfind_parse_args(argc, argv, false); if(gkfs_getsingleserverdir == nullptr) { Loading