Refactored file and fuction names

Now:
tricorn - Optimized SIMD main implementation
tricorn_V1 - Optimized SISD comparison implementation
tricorn_V0 - Unoptimized SIMD implementation
tricorn_V5 - highly optimized SIMD + threading implementation
This commit is contained in:
Nikola
2026-07-13 23:33:26 +02:00
parent e80beb6618
commit 1318798ac2
12 changed files with 347 additions and 381 deletions
+122 -37
View File
@@ -1,11 +1,29 @@
#include "tricorn_V3.h"
#include <immintrin.h>
static unsigned converge(float c_rea, float c_imag, unsigned n);
static void* compute(void * args);
static unsigned converge(float c_real, float c_imag, unsigned n);
static size_t get_thread_count();
// Argument structs passed to the threads for the caluclations
typedef struct{
unsigned n;
_Bool color;
_Bool flipped;
float res;
size_t width;
unsigned char* img;
} const_args;
typedef struct {
float start_r;
float start_i;
size_t start_row;
size_t stop_row;
const_args* consts;
} compute_args;
/**
* @brief Generates a Tricorn fractal image using highly optimized SSE/SIMD instructions, however the algoriithm is not fully optimized.
* @brief Generates a Tricorn fractal image using an optimized SIMD + Threading implementation.
* @param start The starting complex coordinate (bottom-left or top-left depending on height sign).
* @param width The width of the target image in pixels.
* @param height The height of the target image. A negative height reverses the Y-axis calculation.
@@ -15,36 +33,88 @@ static unsigned converge(float c_rea, float c_imag, unsigned n);
* @param img Pointer to the pre-allocated memory buffer where the bitmap data will be stored.
*/
void tricorn_V3(float complex start, size_t width, ssize_t height, float res, unsigned n, _Bool color, unsigned char* img){
int bytesPerColor = color ? 3 : 1;
const ssize_t min_rows_per_thread = 10;
const _Bool flipped = height < 0;
const size_t abs_height = flipped ? -height : height;
// Calculating the threadcount so that it is limited to a reasonable distribution where thread starting doesnt outway calculation cost
size_t thread_count = get_thread_count();
if(abs_height >= min_rows_per_thread)
while((abs_height/thread_count) < min_rows_per_thread) thread_count--;
else thread_count = 1;
const size_t y_step = (abs_height+thread_count-1)/thread_count;
const float start_r = crealf(start);
const float directional_res = (flipped ? -1.0f: 1.0f)*res;
const float start_i = height > 0 ? cimag(start) : cimag(start) + ((abs_height - 1) * res);
const_args* consts = malloc(sizeof(*consts));
consts->width = width;
consts->flipped = flipped;
consts->color = color;
consts->res = res;
consts->img = img;
consts->n = n;
pthread_t threads[thread_count];
for(size_t i = 0; i < thread_count; i++){
compute_args* args = malloc(sizeof(*args));
args->start_r =start_r;
args->start_i =start_i+i*directional_res*y_step;
args->start_row = i*y_step;
args->stop_row = abs_height < (args->start_row+y_step) ? abs_height: (args->start_row+y_step);
args->consts = consts;
if(pthread_create(&threads[i],NULL,compute,args)){
compute(args);
free(args);
args = NULL;
}
}
for(size_t i = 0; i < thread_count; i++){
pthread_join(threads[i],NULL);
}
free(consts);
consts = NULL;
return;
}
static void* compute(void * vargs){
compute_args *args = vargs;
const_args *c = args->consts;
vargs = NULL;
const int bytes_per_color = c->color ? 3 : 1;
// Absolute height for iteration purposes
ssize_t actual_height = height < 0 ? (ssize_t)(-height) : height;
size_t simd_width = (width / 4) * 4;
// Depending on the sign of height we generate the image top-down or bottom up
// As we iterate the image buffer from the top, if height is negative we would have to start at the end of the imaginary part and go in the oposite direction when generating
ssize_t imag_direction = height < 0 ? -1 : 1;
float c_start_real = creal(start);
float c_start_imag = height > 0 ? cimag(start) : cimag(start) + (actual_height - 1) * res;
const size_t simd_width = (c->width / 4) * 4;
float c_start_real = args->start_r;
__m128 four = _mm_set1_ps(4.0f);
__m128 minus_two = _mm_set1_ps(-2.0f);
for(ssize_t y = 0; y < actual_height;y++) {
float c_imag = c_start_imag + (y * imag_direction) * res;
__m128 c_imag_vector = _mm_set1_ps(c_imag);
float c_imag = args->start_i;
__m128 c_imag_vector = _mm_set1_ps(c_imag);
float four_res = 4 * c->res;
float vertical_step = (c->flipped? -1.0f: 1.0f) * c->res;
__m128 vertical_step_vector = _mm_set1_ps(vertical_step);
for(size_t y = args->start_row; y < args->stop_row;y++) {
size_t x = 0;
__m128 c_real_vector = _mm_setr_ps(c_start_real,c_start_real + 1.0f*c->res,c_start_real + 2.0f*c->res, c_start_real + 3.0f*c->res);
for(; x < simd_width;x += 4) { //SIMD Loop
float c_real = c_start_real + (x*res);
__m128 c_real_vector = _mm_setr_ps(c_real,c_real+1.0f*res,c_real+2.0f*res,c_real+3.0f*res);
__m128 real = _mm_setzero_ps();
__m128 imag = _mm_setzero_ps();
__m128i iterations = _mm_setzero_si128();
__m128i active_mask = _mm_set1_epi32(-1);
for(unsigned i = 0; i < n;i++) {
for(unsigned i = 0; i < c->n;i++) {
__m128 real2 = _mm_mul_ps(real,real);
__m128 imag2 = _mm_mul_ps(imag,imag);
@@ -68,7 +138,7 @@ void tricorn_V3(float complex start, size_t width, ssize_t height, float res, un
__m128i times4 = _mm_slli_epi32(mod64, 2);
__m128i values = _mm_sub_epi32(_mm_set1_epi32(255), times4);
__m128i max_iter = _mm_set1_epi32(n);
__m128i max_iter = _mm_set1_epi32(c->n);
__m128i is_max = _mm_cmpeq_epi32(iterations, max_iter);
values = _mm_andnot_si128(is_max, values);
@@ -77,38 +147,44 @@ void tricorn_V3(float complex start, size_t width, ssize_t height, float res, un
for(unsigned i = 0 ; i < 4;i++) {
unsigned int value = value_array[i];
size_t position = (y*width + (x+i)) * bytesPerColor;
size_t position = (y*c->width + (x+i)) * bytes_per_color;
if(!color) {
img[position] = value;
if(!c->color) {
c->img[position] = value;
}
else {
img[position] = ((value*3) % 255);
img[position + 1] = ((value*2) % 255);
img[position + 2] = ((value*5) % 255);
c->img[position] = ((value*3) % 255);
c->img[position + 1] = ((value*2) % 255);
c->img[position + 2] = ((value*5) % 255);
}
}
c_real_vector = _mm_add_ps(c_real_vector,_mm_set1_ps(four_res));
}
for(;x < width; x++) {
float c_real = c_start_real + (x*res);
unsigned int value = converge(c_real,c_imag,n);
for(;x < c->width; x++) {
float c_real = c_start_real + (x*c->res);
unsigned int value = converge(c_real,c_imag,c->n);
size_t position = (y*width + x) * bytesPerColor;
size_t position = (y*c->width + x) * bytes_per_color;
if(!color) {
img[position] = value;
if(!c->color) {
c->img[position] = value;
}
else {
img[position] = ((value*3) % 255);
img[position + 1] = ((value*2) % 255);
img[position + 2] = ((value*5) % 255);
c->img[position] = ((value*3) % 255);
c->img[position + 1] = ((value*2) % 255);
c->img[position + 2] = ((value*5) % 255);
}
}
c_imag += vertical_step;
c_imag_vector = _mm_add_ps(c_imag_vector,vertical_step_vector);
}
free(args);
args = NULL;
return NULL;
}
// Iterative application of the equation given by the assignment
static unsigned converge(float c_real, float c_imag, unsigned n) {
float real = 0.0f;
float imag = 0.0f;
@@ -125,4 +201,13 @@ static unsigned converge(float c_real, float c_imag, unsigned n) {
real = real2 - imag2 + c_real;
}
return 0;
}
static inline size_t get_thread_count(){
#ifdef WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return sysinfo.dwNumberOfProcessors;
#else
return sysconf(_SC_NPROCESSORS_ONLN);
#endif
}