From 50dc2e268f38fcbba6ec326b802b943445d89b44 Mon Sep 17 00:00:00 2001 From: Yi-Ting Shih Date: Wed, 21 Oct 2026 15:10:30 +0800 Subject: [PATCH] init: HW2 --- .clang-format | 36 +++++++ .clang-tidy | 113 +++++++++++++++++++++ part2/Makefile | 34 +++++++ part2/common/cycle_timer.h | 195 ++++++++++++++++++++++++++++++++++++ part2/common/ppm.cpp | 38 +++++++ part2/main.cpp | 190 +++++++++++++++++++++++++++++++++++ part2/mandelbrot_serial.cpp | 102 +++++++++++++++++++ part2/mandelbrot_thread.cpp | 107 ++++++++++++++++++++ 8 files changed, 815 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 part2/Makefile create mode 100644 part2/common/cycle_timer.h create mode 100644 part2/common/ppm.cpp create mode 100644 part2/main.cpp create mode 100644 part2/mandelbrot_serial.cpp create mode 100644 part2/mandelbrot_thread.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4bbf5df --- /dev/null +++ b/.clang-format @@ -0,0 +1,36 @@ +--- +Language: Cpp + +IndentWidth: 4 +UseTab: Never + +# Maximum line length before wrapping, 80 ~ 120 is common. +ColumnLimit: 100 + +IndentCaseLabels: true + +# Force pointers away from the type. +DerivePointerAlignment: false +PointerAlignment: Right + +AlignConsecutiveMacros: Consecutive + +# Allow short blocks and function definitions on a single line only if they're empty. +# This improves readability since single-line blocks are easier to overlook. +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Empty + +# Pack function call arguments into as few lines as possible. +BinPackArguments: true +# Do not pack function declaration parameters into a single line. +# This helps readability when function signatures are long. +BinPackParameters: false + +# Place binary operators at the beginning of the line to make them more visible. +BreakBeforeBinaryOperators: All +# Always break before braces. +BreakBeforeBraces: Allman + +# Separate definition blocks with a blank line so they are easier to be identified. +SeparateDefinitionBlocks: Always +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..be42c8f --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,113 @@ +# Here is an explanation for why some of the checks are disabled: +# +# -bugprone-casting-through-void +# defines macro that generates casting through void. +# +# -bugprone-easily-swappable-parameters +# Required big refactoring in the codebase. It was not worth it. +# +# -bugprone-implicit-widening-of-multiplication-result +# This makes the code too verbose. It's unlikely we're going to overflow. +# +# -clang-analyzer-optin.taint.TaintedAlloc +# Tainted allocations are used on the nodes where we currently don't have a +# good limit on the number of nodes. +# +# -performance-enum-size +# Keeping the code simple is more important than performance in this case. +# +# -performance-inefficient-string-concatenation +# It's common that we concatenate strings with +. For cases other than +# loops, using `append` is not worth it and makes the code less readable. +# +# -modernize-loop-convert +# As range-based for loops are only available since OpenMP 5, +# this check should not be used on code with a compatibility requirement of OpenMP prior to version 5. +# +# -modernize-use-auto: +# The use of auto is not always beneficial. +# It can make the code less readable and harder to understand. +# +# -modernize-use-nodiscard: +# The leading [[nodiscard]] attribute adds noise to the code when reading it +# and may not provide considerable benefits. +# +# -modernize-use-trailing-return-type: +# Most of the times, trailing return types provide no benefit. +# The additional "auto" and "->" are just noise. +# +# NOTE: +# This codebase heavily interacts with standard C libraries, so most of the `cppcoreguidelines` +# checks are disabled. In particular, +# cppcoreguidelines-init-variables: Many C library functions use output parameters. +# We intentionally avoid initializing variables passed to them, +# so that we get warnings if those variables are used before being properly set. +# cppcoreguidelines-pro-type-vararg: Variadic functions are used, as pre-C++20 lacks sufficient +# formatting support. + +Checks: > + -*, + bugprone-*, + -bugprone-casting-through-void, + -bugprone-easily-swappable-parameters, + -bugprone-implicit-widening-of-multiplication-result, + performance-*, + -performance-enum-size, + -performance-inefficient-string-concatenation, + cppcoreguidelines-macro-usage, + cppcoreguidelines-no-malloc, + cppcoreguidelines-pro-type-cstyle-cast, + clang-analyzer-*, + modernize-*, + -modernize-loop-convert, + -modernize-use-auto, + -modernize-use-nodiscard, + -modernize-use-trailing-return-type, + readability-avoid-const-params-in-decls, + readability-avoid-nested-conditional-operator, + readability-avoid-return-with-void-value, + readability-avoid-unconditional-preprocessor-if, + readability-container-size-empty, + readability-delete-null-pointer, + readability-duplicate-include, + readability-else-after-return, + readability-identifier-naming, + readability-inconsistent-declaration-parameter-name, + readability-magic-numbers + readability-make-member-function-const, + readability-math-missing-parentheses, + readability-misleading-indentation, + readability-non-const-parameter, + readability-redundant-casting, + readability-redundant-control-flow, + readability-redundant-declaration, + readability-redundant-function-ptr-dereference, + readability-redundant-inline-specifier, + readability-redundant-preprocessor, + readability-reference-to-constructed-temporary, + readability-simplify-boolean-expr, + readability-simplify-subscript-expr, + readability-static-accessed-through-instance, + readability-static-definition-in-anonymous-namespace, + readability-string-compare, + +# For details on the checks, see: +# https://clang.llvm.org/extra/clang-tidy/checks/list.html + +CheckOptions: + - { key: readability-identifier-naming.ClassCase, value: CamelCase } + - { key: readability-identifier-naming.StructCase, value: CamelCase } + - { key: readability-identifier-naming.UnionCase, value: CamelCase } + - { key: readability-identifier-naming.EnumCase, value: CamelCase } + - { key: readability-identifier-naming.TypeAliasCase, value: CamelCase } + - { key: readability-identifier-naming.TypedefCase, value: CamelCase } + - { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase } + - { key: readability-identifier-naming.FunctionCase, value: lower_case } + - { key: readability-identifier-naming.MethodCase, value: lower_case } + - { key: readability-identifier-naming.VariableCase, value: lower_case } + - { key: readability-identifier-naming.ParameterCase, value: lower_case } + - { key: readability-identifier-naming.ConstantCase, value: UPPER_CASE } + - { key: readability-identifier-naming.ConstantParameterCase, value: lower_case } + - { key: readability-identifier-naming.LocalConstantCase, value: lower_case } + - { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE } + - { key: readability-identifier-naming.NamespaceCase, value: lower_case } diff --git a/part2/Makefile b/part2/Makefile new file mode 100644 index 0000000..8479ace --- /dev/null +++ b/part2/Makefile @@ -0,0 +1,34 @@ +APP_NAME = mandelbrot +OBJDIR = objs +COMMONDIR = ./common + +CXX = g++ -m64 +CXXFLAGS = -I$(COMMONDIR) -I$(OBJDIR) -O3 -std=c++17 -Wall +LDLIBS = -lm -lpthread + +PPM_CXX = $(COMMONDIR)/ppm.cpp +PPM_OBJ = $(addprefix $(OBJDIR)/, $(subst $(COMMONDIR)/,, $(PPM_CXX:.cpp=.o))) + + +default: $(APP_NAME) + +dirs: + mkdir -p $(OBJDIR)/ +.PHONY: dirs + +clean: + $(RM) -r $(OBJDIR) *.ppm *~ $(APP_NAME) +.PHONY: clean + +OBJS = $(OBJDIR)/main.o $(OBJDIR)/mandelbrot_serial.o $(OBJDIR)/mandelbrot_thread.o $(PPM_OBJ) + +$(APP_NAME): dirs $(OBJS) + $(CXX) $(CXXFLAGS) -o $@ $(OBJS) $(LDLIBS) + +$(OBJDIR)/%.o: %.cpp + $(CXX) $< $(CXXFLAGS) -c -o $@ + +$(OBJDIR)/%.o: $(COMMONDIR)/%.cpp + $(CXX) $< $(CXXFLAGS) -c -o $@ + +$(OBJDIR)/main.o: $(COMMONDIR)/cycle_timer.h diff --git a/part2/common/cycle_timer.h b/part2/common/cycle_timer.h new file mode 100644 index 0000000..676cad3 --- /dev/null +++ b/part2/common/cycle_timer.h @@ -0,0 +1,195 @@ +#ifndef SYRAH_CYCLE_TIMER_H +#define SYRAH_CYCLE_TIMER_H + +#if defined(__APPLE__) +#if defined(__x86_64__) +#include +#else +#include +#include +#endif // __x86_64__ or not + +#include // fprintf +#include // exit + +#elif _WIN32 +#include +#include +#else +#include +#include +#include +#include +#endif + +// This uses the cycle counter of the processor. Different +// processors in the system will have different values for this. If +// you process moves across processors, then the delta time you +// measure will likely be incorrect. This is mostly for fine +// grained measurements where the process is likely to be on the +// same processor. For more global things you should use the +// Time interface. + +// Also note that if you processors' speeds change (i.e. processors +// scaling) or if you are in a heterogenous environment, you will +// likely get spurious results. +class CycleTimer +{ + public: + using SysClock = unsigned long long; + + ////////// + // Return the current CPU time, in terms of clock ticks. + // Time zero is at some arbitrary point in the past. + static SysClock current_ticks() + { +#if defined(__APPLE__) && !defined(__x86_64__) + return mach_absolute_time(); +#elif defined(_WIN32) + LARGE_INTEGER qwTime; + QueryPerformanceCounter(&qwTime); + return qwTime.QuadPart; +#elif defined(__x86_64__) + unsigned int a, d; + asm volatile("rdtsc" : "=a"(a), "=d"(d)); + return static_cast(a) | (static_cast(d) << 32); +#elif defined(__ARM_NEON__) && 0 // mrc requires superuser. + unsigned int val; + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(val)); + return val; +#else + timespec spec; + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &spec); + return CycleTimer::SysClock(static_cast(spec.tv_sec) * 1e9 + + static_cast(spec.tv_nsec)); +#endif + } + + ////////// + // Return the current CPU time, in terms of seconds. + // This is slower than current_ticks(). Time zero is at + // some arbitrary point in the past. + static double current_seconds() + { + return static_cast(current_ticks()) * seconds_per_tick(); + } + + ////////// + // Return the conversion from seconds to ticks. + static double ticks_per_second() + { + return 1.0 / seconds_per_tick(); + } + + static const char *tick_units() + { +#if defined(__APPLE__) && !defined(__x86_64__) + return "ns"; +#elif defined(__WIN32__) || defined(__x86_64__) + return "cycles"; +#else + return "ns"; // clock_gettime +#endif + } + + ////////// + // Return the conversion from ticks to seconds. + static double seconds_per_tick() + { + static bool initialized = false; + static double seconds_per_tick_val; + if (initialized) + return seconds_per_tick_val; +#if defined(__APPLE__) +#ifdef __x86_64__ + int args[] = {CTL_HW, HW_CPU_FREQ}; + unsigned int Hz; + size_t len = sizeof(Hz); + if (sysctl(args, 2, &Hz, &len, NULL, 0) != 0) + { + fprintf(stderr, "Failed to initialize seconds_per_tick_val!\n"); + exit(-1); + } + seconds_per_tick_val = 1.0 / (double)Hz; +#else + mach_timebase_info_data_t time_info; + mach_timebase_info(&time_info); + + // Scales to nanoseconds without 1e-9f + seconds_per_tick_val + = (1e-9 * static_cast(time_info.numer)) / static_cast(time_info.denom); +#endif // x86_64 or not +#elif defined(_WIN32) + LARGE_INTEGER qwTicksPerSec; + QueryPerformanceFrequency(&qwTicksPerSec); + seconds_per_tick_val = 1.0 / static_cast(qwTicksPerSec.QuadPart); +#else + FILE *fp = fopen("/proc/cpuinfo", "r"); + char input[1024]; // NOLINT(modernize-avoid-c-arrays): Extensively working with C libraries. + if (!fp) + { + fprintf(stderr, "%s failed: couldn't find /proc/cpuinfo.", __PRETTY_FUNCTION__); + exit(-1); + } + // In case we don't find it, e.g. on the N900 + seconds_per_tick_val = 1e-9; + while (!feof(fp) && fgets(input, 1024, fp)) + { + // NOTE(boulos): Because reading cpuinfo depends on dynamic + // frequency scaling it's better to read the @ sign first + float ghz, mhz; + if (strstr(input, "model name")) + { + char *at_sign = strstr(input, "@"); + if (at_sign) + { + char *after_at = at_sign + 1; + char *ghz_str = strstr(after_at, "ghz"); + char *mhz_str = strstr(after_at, "mhz"); + if (ghz_str) + { + *ghz_str = '\0'; + if (1 == sscanf(after_at, "%f", &ghz)) + { + // printf("GHz = %f\n", ghz); + seconds_per_tick_val = 1e-9f / ghz; + break; + } + } + else if (mhz_str) + { + *mhz_str = '\0'; + if (1 == sscanf(after_at, "%f", &mhz)) + { + // printf("MHz = %f\n", mhz); + seconds_per_tick_val = 1e-6f / mhz; + break; + } + } + } + } + else if (1 == sscanf(input, "cpu MHz : %f", &mhz)) + { + // printf("MHz = %f\n", mhz); + seconds_per_tick_val = 1e-6f / mhz; + break; + } + } + fclose(fp); +#endif + + initialized = true; + return seconds_per_tick_val; + } + + ////////// + // Return the conversion from ticks to milliseconds. + static double ms_per_tick() + { + return seconds_per_tick() * 1000.0; + } + + CycleTimer() = delete; +}; + +#endif // SYRAH_CYCLE_TIMER_H diff --git a/part2/common/ppm.cpp b/part2/common/ppm.cpp new file mode 100644 index 0000000..c582faf --- /dev/null +++ b/part2/common/ppm.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +void write_ppm_image(int *data, int width, int height, const char *filename, int max_iterations) +{ + FILE *fp = fopen(filename, "wb"); + if (!fp) + { + perror("fopen() failed"); + exit(EXIT_FAILURE); + } + + // write ppm header + fprintf(fp, "P6\n"); + fprintf(fp, "%d %d\n", width, height); + fprintf(fp, "255\n"); + + for (int i = 0; i < width * height; ++i) + { + + // Clamp iteration count for this pixel, then scale the value + // to 0-1 range. Raise resulting value to a power (<1) to + // increase brightness of low iteration count + // pixels. a.k.a. Make things look cooler. + + float mapped = std::pow( + std::min(static_cast(max_iterations), static_cast(data[i])) / 256.f, .5f); + + // convert back into 0-255 range, 8-bit channels + unsigned char result = static_cast(255.f * mapped); + for (int j = 0; j < 3; ++j) + fputc(result, fp); + } + fclose(fp); + printf("Wrote image file %s\n", filename); +} diff --git a/part2/main.cpp b/part2/main.cpp new file mode 100644 index 0000000..be7766a --- /dev/null +++ b/part2/main.cpp @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include + +#include "cycle_timer.h" + +extern void mandelbrot_serial(float x0, + float y0, + float x1, + float y1, + int width, + int height, + int start_row, + int num_rows, + int max_iterations, + int *output); + +extern void mandelbrot_thread(int num_threads, + float x0, + float y0, + float x1, + float y1, + int width, + int height, + int max_iterations, + int *output); + +extern void +write_ppm_image(int *data, int width, int height, const char *filename, int max_iterations); + +void scale_and_shift( + float &x0, float &x1, float &y0, float &y1, float scale, float shift_x, float shift_y) +{ + + x0 *= scale; + x1 *= scale; + y0 *= scale; + y1 *= scale; + x0 += shift_x; + x1 += shift_x; + y0 += shift_y; + y1 += shift_y; +} + +void usage(const char *progname) +{ + printf("Usage: %s [options]\n", progname); + printf("Program Options:\n"); + printf(" -t --threads Use N threads (Default = 2)\n"); + printf(" -v --view Use specified view settings (Default = 1)\n"); + printf(" -? --help This message\n"); +} + +bool verify_result(int *gold, int *result, int width, int height) +{ + + int i, j; + + for (i = 0; i < height; i++) + { + for (j = 0; j < width; j++) + { + if (gold[(i * width) + j] != result[(i * width) + j]) + { + printf("Mismatch : [%d][%d], Expected : %d, Actual : %d\n", i, j, + gold[(i * width) + j], result[(i * width) + j]); + return false; + } + } + } + + return true; +} + +int main(int argc, char **argv) +{ + + const unsigned int width = 1600; + const unsigned int height = 1200; + const int max_iterations = 256; + int num_threads = 2; + + float x0 = -2; + float x1 = 1; + float y0 = -1; + float y1 = 1; + + // parse commandline options //////////////////////////////////////////// + int opt; + // NOLINTNEXTLINE(modernize-avoid-c-arrays): Required by C function. + static struct option long_options[] = {{"threads", 1, nullptr, 't'}, + {"view", 1, nullptr, 'v'}, + {"help", 0, nullptr, '?'}, + {nullptr, 0, nullptr, 0}}; + + while ((opt = getopt_long(argc, argv, "t:v:?", long_options, nullptr)) != EOF) + { + + switch (opt) + { + case 't': + { + num_threads = atoi(optarg); + break; + } + case 'v': + { + int view_index = atoi(optarg); + // change view settings + if (view_index == 2) + { + float scale_value = .015f; + float shift_x = -.986f; + float shift_y = .30f; + scale_and_shift(x0, x1, y0, y1, scale_value, shift_x, shift_y); + } + else if (view_index > 1) + { + fprintf(stderr, "Invalid view index\n"); + return 1; + } + break; + } + case '?': + default: + usage(argv[0]); + return 1; + } + } + // end parsing of commandline options + + int *output_serial = new int[width * height]; + int *output_thread = new int[width * height]; + + // + // Run the serial implementation. Run the code three times and + // take the minimum to get a good estimate. + // + + double min_serial = 1e30; + for (int i = 0; i < 5; ++i) + { + memset(output_serial, 0, width * height * sizeof(int)); + double start_time = CycleTimer::current_seconds(); + mandelbrot_serial(x0, y0, x1, y1, width, height, 0, height, max_iterations, output_serial); + double end_time = CycleTimer::current_seconds(); + min_serial = std::min(min_serial, end_time - start_time); + } + + printf("[mandelbrot serial]:\t\t[%.3f] ms\n", min_serial * 1000); + write_ppm_image(output_serial, width, height, "mandelbrot-serial.ppm", max_iterations); + + // + // Run the threaded version + // + + double min_thread = 1e30; + for (int i = 0; i < 5; ++i) + { + memset(output_thread, 0, width * height * sizeof(int)); + double start_time = CycleTimer::current_seconds(); + mandelbrot_thread(num_threads, x0, y0, x1, y1, width, height, max_iterations, + output_thread); + double end_time = CycleTimer::current_seconds(); + min_thread = std::min(min_thread, end_time - start_time); + } + + printf("[mandelbrot thread]:\t\t[%.3f] ms\n", min_thread * 1000); + write_ppm_image(output_thread, width, height, "mandelbrot-thread.ppm", max_iterations); + + if (!verify_result(output_serial, output_thread, width, height)) + { + printf("Error : Output from threads does not match serial output\n"); + + delete[] output_serial; + delete[] output_thread; + + return 1; + } + + // compute speedup + printf("\t\t\t\t(%.2fx speedup from %d threads)\n", min_serial / min_thread, num_threads); + + delete[] output_serial; + delete[] output_thread; + + return 0; +} diff --git a/part2/mandelbrot_serial.cpp b/part2/mandelbrot_serial.cpp new file mode 100644 index 0000000..ed26880 --- /dev/null +++ b/part2/mandelbrot_serial.cpp @@ -0,0 +1,102 @@ +/* + + Note: This code was modified from example code + originally provided by Intel. To comply with Intel's open source + licensing agreement, their copyright is retained below. + + ----------------------------------------------------------------- + + Copyright (c) 2010-2011, Intel Corporation + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Intel Corporation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +namespace +{ + +int mandel(float c_re, float c_im, int count) +{ + float z_re = c_re, z_im = c_im; + int i; + for (i = 0; i < count; ++i) + { + + if (z_re * z_re + z_im * z_im > 4.f) + break; + + float new_re = (z_re * z_re) - (z_im * z_im); + float new_im = 2.f * z_re * z_im; + z_re = c_re + new_re; + z_im = c_im + new_im; + } + + return i; +} + +} // namespace + +// +// MandelbrotSerial -- +// +// Compute an image visualizing the mandelbrot set. The resulting +// array contains the number of iterations required before the complex +// number corresponding to a pixel could be rejected from the set. +// +// * x0, y0, x1, y1 describe the complex coordinates mapping +// into the image viewport. +// * width, height describe the size of the output image +// * startRow, totalRows describe how much of the image to compute +void mandelbrot_serial(float x0, + float y0, + float x1, + float y1, + int width, + int height, + int start_row, + int total_rows, + int max_iterations, + int *output) +{ + float dx = (x1 - x0) / (float)width; + float dy = (y1 - y0) / (float)height; + + int end_row = start_row + total_rows; + + for (int j = start_row; j < end_row; j++) + { + for (int i = 0; i < width; ++i) + { + float x = x0 + ((float)i * dx); + float y = y0 + ((float)j * dy); + + int index = ((j * width) + i); + output[index] = mandel(x, y, max_iterations); + } + } +} diff --git a/part2/mandelbrot_thread.cpp b/part2/mandelbrot_thread.cpp new file mode 100644 index 0000000..2a8761b --- /dev/null +++ b/part2/mandelbrot_thread.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include + +struct WorkerArgs +{ + float x0, x1; + float y0, y1; + unsigned int width; + unsigned int height; + int maxIterations; + int *output; + int threadId; + int numThreads; +}; + +extern void mandelbrot_serial(float x0, + float y0, + float x1, + float y1, + int width, + int height, + int start_row, + int num_rows, + int max_iterations, + int *output); + +// +// worker_thread_start -- +// +// Thread entrypoint. +void worker_thread_start(WorkerArgs *const args) +{ + + // TODO FOR PP STUDENTS: Implement the body of the worker + // thread here. Each thread could make a call to mandelbrot_serial() + // to compute a part of the output image. For example, in a + // program that uses two threads, thread 0 could compute the top + // half of the image and thread 1 could compute the bottom half. + // Of course, you can copy mandelbrot_serial() to this file and + // modify it to pursue a better performance. + + printf("Hello world from thread %d\n", args->threadId); +} + +// +// mandelbrot_thread -- +// +// Multi-threaded implementation of mandelbrot set image generation. +// Threads of execution are created by spawning std::threads. +void mandelbrot_thread(int num_threads, + float x0, + float y0, + float x1, + float y1, + int width, + int height, + int max_iterations, + int *output) +{ + static constexpr int max_threads = 32; + + if (num_threads > max_threads) + { + fprintf(stderr, "Error: Max allowed threads is %d\n", max_threads); + exit(1); + } + + // Creates thread objects that do not yet represent a thread. + std::array workers; + std::array args = {}; + + for (int i = 0; i < num_threads; i++) + { + // TODO FOR PP STUDENTS: You may or may not wish to modify + // the per-thread arguments here. The code below copies the + // same arguments for each thread + args[i].x0 = x0; + args[i].y0 = y0; + args[i].x1 = x1; + args[i].y1 = y1; + args[i].width = width; + args[i].height = height; + args[i].maxIterations = max_iterations; + args[i].numThreads = num_threads; + args[i].output = output; + + args[i].threadId = i; + } + + // Spawn the worker threads. Note that only numThreads-1 std::threads + // are created and the main application thread is used as a worker + // as well. + for (int i = 1; i < num_threads; i++) + { + workers[i] = std::thread(worker_thread_start, &args[i]); + } + + worker_thread_start(&args[0]); + + // join worker threads + for (int i = 1; i < num_threads; i++) + { + workers[i].join(); + } +}