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
+9 -26
View File
@@ -14,8 +14,7 @@
#include "tricorn_V1.h" #include "tricorn_V1.h"
#include "tricorn_V2.h" #include "tricorn_V2.h"
#include "tricorn_V3.h" #include "tricorn_V3.h"
#include "tricorn_V4.h"
#include "tricorn_V5.h"
void save_bmp(const char *filename, unsigned char *img, size_t width, ssize_t height,_Bool color); void save_bmp(const char *filename, unsigned char *img, size_t width, ssize_t height,_Bool color);
@@ -28,29 +27,21 @@ void benchmarking(size_t benchmarking_amount, int version_to_use, float complex
switch (version_to_use) switch (version_to_use)
{ {
case 0: // Uses highly optimized SIMD and threaded version / main implementation case 0: // Uses highly optimized SIMD and threaded version / main implementation
printf("Benchmarking V0 - highly optimized SIMD + Threading implementation\n"); printf("Benchmarking V0 - highly optimized SIMD\n");
tricorn_implementation = tricorn; tricorn_implementation = tricorn;
break; break;
case 1: // Uses unoptimised scalar comparison implementation case 1: // Uses unoptimised scalar comparison implementation
printf("Benchmarking V1 - unoptimized comparison implementation\n"); printf("Benchmarking V1 - scalar comparison implementation\n");
tricorn_implementation = tricorn_V1; tricorn_implementation = tricorn_V1;
break; break;
case 2: // Uses optimized SISD implementation case 2: // Uses optimized SISD implementation
printf("Benchmarking V2 - optimized SISD implementation\n"); printf("Benchmarking V2 - unoptimized SIMD implementation\n");
tricorn_implementation = tricorn_V2; tricorn_implementation = tricorn_V2;
break; break;
case 3: // Uses unoptimized SIMD implementation case 3: // Uses unoptimized SIMD implementation
printf("Benchmarking V3 - unoptimized SIMD implementation\n"); printf("Benchmarking V3 - highly optimized SIMD + Threading implementation\n");
tricorn_implementation = tricorn_V3; tricorn_implementation = tricorn_V3;
break; break;
case 4: // Uses highly optimized SIMD
printf("Benchmarking V4 - optimized SIMD implementation\n");
tricorn_implementation = tricorn_V4;
break;
case 5: // Experimental cleanups and inlines / for testing
printf("Benchmarking V5 - experimental implementation\n");
tricorn_implementation = tricorn_V5;
break;
default: default:
fprintf(stderr,"Invalid version to use: V%d. Available versions are : V0,V1,V2,V3,V4,V5\n",version_to_use); fprintf(stderr,"Invalid version to use: V%d. Available versions are : V0,V1,V2,V3,V4,V5\n",version_to_use);
return; return;
@@ -433,29 +424,21 @@ int main(int argc, char** argv)
switch (version_to_use) switch (version_to_use)
{ {
case 0: case 0:
// Uses highly optimized SIMD and threaded version / main implementation // Uses highly optimized SIMD - main implementation
tricorn(complexA,width,height,resolution,n,colored,tempArr); tricorn(complexA,width,height,resolution,n,colored,tempArr);
break; break;
case 1: case 1:
// Uses unoptimised comparison implementation // Uses scalar comparison implementation
tricorn_V1(complexA,width,height,resolution,n,colored,tempArr); tricorn_V1(complexA,width,height,resolution,n,colored,tempArr);
break; break;
case 2: case 2:
// Uses optimized SISD version // Uses unoptimized SIMD implementation
tricorn_V2(complexA,width,height,resolution,n,colored,tempArr); tricorn_V2(complexA,width,height,resolution,n,colored,tempArr);
break; break;
case 3: case 3:
// Uses unoptimized SIMD version // Uses highly optimized SIMD + Threading implementation
tricorn_V3(complexA,width,height,resolution,n,colored,tempArr); tricorn_V3(complexA,width,height,resolution,n,colored,tempArr);
break; break;
case 4:
// Uses highly optimized SIMD
tricorn_V4(complexA,width,height,resolution,n,colored,tempArr);
break;
case 5:
// Experimental cleanups and inlines / for testing
tricorn_V5(complexA,width,height,resolution,n,colored,tempArr);
break;
default: default:
break; break;
} }
+35 -115
View File
@@ -1,29 +1,11 @@
#include "tricorn.h" #include "tricorn.h"
#include <immintrin.h>
static void* compute(void * args);
static unsigned converge(float c_real, float c_imag, unsigned n); 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 an optimized SIMD + Threading implementation. * @brief Generates a Tricorn fractal image using an optimized SIMD algorithm
* @param start The starting complex coordinate (bottom-left or top-left depending on height sign). * @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 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. * @param height The height of the target image. A negative height reverses the Y-axis calculation.
@@ -33,88 +15,39 @@ typedef struct {
* @param img Pointer to the pre-allocated memory buffer where the bitmap data will be stored. * @param img Pointer to the pre-allocated memory buffer where the bitmap data will be stored.
*/ */
void tricorn(float complex start, size_t width, ssize_t height, float res, unsigned n, _Bool color, unsigned char* img){ void tricorn(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 // Absolute height for iteration purposes
const size_t simd_width = (c->width / 4) * 4; 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;
float c_start_real = args->start_r;
__m128 four = _mm_set1_ps(4.0f); __m128 four = _mm_set1_ps(4.0f);
__m128 minus_two = _mm_set1_ps(-2.0f); __m128 minus_two = _mm_set1_ps(-2.0f);
float c_imag = args->start_i; float c_imag = c_start_imag;
__m128 c_imag_vector = _mm_set1_ps(c_imag); __m128 c_imag_vector = _mm_set1_ps(c_imag);
float four_res = 4 * c->res; float four_res = 4 * res;
float vertical_step = (c->flipped? -1.0f: 1.0f) * c->res;
float vertical_step = imag_direction * res;
__m128 vertical_step_vector = _mm_set1_ps(vertical_step); __m128 vertical_step_vector = _mm_set1_ps(vertical_step);
for(size_t y = args->start_row; y < args->stop_row;y++) { for(ssize_t y = 0; y < actual_height;y++) {
size_t x = 0; 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); __m128 c_real_vector = _mm_setr_ps(c_start_real,c_start_real + 1.0f*res,c_start_real + 2.0f*res, c_start_real + 3.0f*res);
for(; x < simd_width;x += 4) { //SIMD Loop for(; x < simd_width;x += 4) { //SIMD Loop
__m128 real = _mm_setzero_ps(); __m128 real = _mm_setzero_ps();
__m128 imag = _mm_setzero_ps(); __m128 imag = _mm_setzero_ps();
__m128i iterations = _mm_setzero_si128(); __m128i iterations = _mm_setzero_si128();
__m128i active_mask = _mm_set1_epi32(-1); __m128i active_mask = _mm_set1_epi32(-1);
for(unsigned i = 0; i < c->n;i++) { for(unsigned i = 0; i < n;i++) {
__m128 real2 = _mm_mul_ps(real,real); __m128 real2 = _mm_mul_ps(real,real);
__m128 imag2 = _mm_mul_ps(imag,imag); __m128 imag2 = _mm_mul_ps(imag,imag);
@@ -138,7 +71,7 @@ static void* compute(void * vargs){
__m128i times4 = _mm_slli_epi32(mod64, 2); __m128i times4 = _mm_slli_epi32(mod64, 2);
__m128i values = _mm_sub_epi32(_mm_set1_epi32(255), times4); __m128i values = _mm_sub_epi32(_mm_set1_epi32(255), times4);
__m128i max_iter = _mm_set1_epi32(c->n); __m128i max_iter = _mm_set1_epi32(n);
__m128i is_max = _mm_cmpeq_epi32(iterations, max_iter); __m128i is_max = _mm_cmpeq_epi32(iterations, max_iter);
values = _mm_andnot_si128(is_max, values); values = _mm_andnot_si128(is_max, values);
@@ -147,44 +80,40 @@ static void* compute(void * vargs){
for(unsigned i = 0 ; i < 4;i++) { for(unsigned i = 0 ; i < 4;i++) {
unsigned int value = value_array[i]; unsigned int value = value_array[i];
size_t position = (y*c->width + (x+i)) * bytes_per_color; size_t position = (y*width + (x+i)) * bytesPerColor;
if(!c->color) { if(!color) {
c->img[position] = value; img[position] = value;
} }
else { else {
c->img[position] = ((value*3) % 255); img[position] = ((value*3) % 255);
c->img[position + 1] = ((value*2) % 255); img[position + 1] = ((value*2) % 255);
c->img[position + 2] = ((value*5) % 255); img[position + 2] = ((value*5) % 255);
} }
} }
c_real_vector = _mm_add_ps(c_real_vector,_mm_set1_ps(four_res)); c_real_vector = _mm_add_ps(c_real_vector,_mm_set1_ps(four_res));
} }
for(;x < c->width; x++) { for(;x < width; x++) {
float c_real = c_start_real + (x*c->res); float c_real = c_start_real + (x*res);
unsigned int value = converge(c_real,c_imag,c->n); unsigned int value = converge(c_real,c_imag,n);
size_t position = (y*c->width + x) * bytes_per_color; size_t position = (y*width + x) * bytesPerColor;
if(!c->color) { if(!color) {
c->img[position] = value; img[position] = value;
} }
else { else {
c->img[position] = ((value*3) % 255); img[position] = ((value*3) % 255);
c->img[position + 1] = ((value*2) % 255); img[position + 1] = ((value*2) % 255);
c->img[position + 2] = ((value*5) % 255); img[position + 2] = ((value*5) % 255);
} }
} }
c_imag += vertical_step; c_imag += vertical_step;
c_imag_vector = _mm_add_ps(c_imag_vector,vertical_step_vector); 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) { static unsigned converge(float c_real, float c_imag, unsigned n) {
float real = 0.0f; float real = 0.0f;
float imag = 0.0f; float imag = 0.0f;
@@ -201,13 +130,4 @@ static unsigned converge(float c_real, float c_imag, unsigned n) {
real = real2 - imag2 + c_real; real = real2 - imag2 + c_real;
} }
return 0; 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
} }
+2 -8
View File
@@ -2,12 +2,6 @@
#include <stddef.h> #include <stddef.h>
#include <math.h> #include <math.h>
#include <sys/types.h> #include <sys/types.h>
#include<stdbool.h>
#include <immintrin.h> #include <immintrin.h>
#include <pthread.h> void tricorn(float complex start, size_t width, ssize_t height, float res, unsigned n, bool color, unsigned char* img);
#ifdef WIN32
#include <sysinfoapi.h>
#else
#include <unistd.h>
#endif
void tricorn(float complex start, size_t width, ssize_t height, float res, unsigned n, _Bool color, unsigned char* img);
+77
View File
@@ -0,0 +1,77 @@
#include "tricorn_V1.h"
static unsigned converge(float c_real, float c_imag, unsigned n);
/**
* @brief Generates a Tricorn fractal image using an optimized scalar 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.
* @param res The step size (resolution) per pixel in the complex plane.
* @param n The maximum number of mathematical iterations per pixel.
* @param color If true, generates a 24-bit RGB image. If false, writes a grayscale image.
* @param img Pointer to the pre-allocated memory buffer where the bitmap data will be stored.
*/
void tricorn_V1(float complex start, size_t width, ssize_t height, float res, unsigned n, _Bool color, unsigned char* img){
int bytesPerColor = color ? 3 : 1;
// Absolute height for iteration purposes
ssize_t actual_height = height > 0 ? height : (ssize_t)(-height);
// Depending on the sign of height we generate the image top-down or bottom up
// As we iterate from the top always, if it 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);;
float vertical_step = imag_direction * res;
size_t position = 0;
float c_imag = c_start_imag;
// Simple iteration over every pixel in the picture
for(ssize_t y = 0; y < actual_height; y++){
// Calculate imaginary part on each row as it stays the same
float c_real = c_start_real;
for(size_t x = 0; x < width; x++){
unsigned value = converge(c_real,c_imag,n);
// Pixel value position in the output array
if(!color){ // Grayscale colors as specified by the assignment r = g = b
img[position] = value;
}
else{ // Temporary colors with random multipliers
img[position] = ((value*3) % 255);
img[position + 1] = ((value*2) % 255) ;
img[position + 2] = ((value*5) % 255);
}
position += bytesPerColor;
c_real += res;
}
c_imag += vertical_step;
}
}
// Iterative application of the equation given by the assignment
static inline unsigned converge(float c_real, float c_imag, unsigned n) {
float real = 0.0f;
float imag = 0.0f;
for (unsigned i = 0; i < n; i++) {
float real2 = real * real;
float imag2 = imag * imag;
if (real2 + imag2 > 4.0f) {
return 255 - 4 * (i % 64);
}
imag = -2.0f * real * imag + c_imag;
real = real2 - imag2 + c_real;
}
return 0;
}
+5 -5
View File
@@ -1,5 +1,5 @@
#include <complex.h> #include <complex.h>
#include <stddef.h> #include <stddef.h>
#include <math.h> #include <math.h>
#include <sys/types.h> #include <sys/types.h>
void tricorn_V1(float complex start, size_t width, ssize_t height, float res, unsigned n, _Bool color, unsigned char* img); void tricorn_V1(float complex start, size_t width, ssize_t height, float res, unsigned n, _Bool color, unsigned char* img);
+78 -27
View File
@@ -1,10 +1,11 @@
#include "tricorn_V2.h" #include "tricorn_V2.h"
#include <immintrin.h>
static unsigned converge(float c_real, float c_imag, unsigned n); static unsigned converge(float c_rea, float c_imag, unsigned n);
/** /**
* @brief Generates a Tricorn fractal image using an optimized scalar implementation. * @brief Generates a Tricorn fractal image using highly optimized SSE/SIMD instructions, however the algoriithm is not fully optimized.
* @param start The starting complex coordinate (bottom-left or top-left depending on height sign). * @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 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. * @param height The height of the target image. A negative height reverses the Y-axis calculation.
@@ -17,48 +18,98 @@ void tricorn_V2(float complex start, size_t width, ssize_t height, float res, un
int bytesPerColor = color ? 3 : 1; int bytesPerColor = color ? 3 : 1;
// Absolute height for iteration purposes // Absolute height for iteration purposes
ssize_t actual_height = height > 0 ? height : (ssize_t)(-height); 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 // Depending on the sign of height we generate the image top-down or bottom up
// As we iterate from the top always, if it is negative we would have to start at the end of the imaginary part and go in the oposite direction when generating // 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; ssize_t imag_direction = height < 0 ? -1 : 1;
float c_start_real = creal(start); float c_start_real = creal(start);
float c_start_imag = height > 0 ? cimag(start) : cimag(start) + ((actual_height-1) * res);; float c_start_imag = height > 0 ? cimag(start) : cimag(start) + (actual_height - 1) * res;
__m128 four = _mm_set1_ps(4.0f);
__m128 minus_two = _mm_set1_ps(-2.0f);
float vertical_step = imag_direction * res; for(ssize_t y = 0; y < actual_height;y++) {
size_t position = 0; float c_imag = c_start_imag + (y * imag_direction) * res;
__m128 c_imag_vector = _mm_set1_ps(c_imag);
size_t x = 0;
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);
float c_imag = c_start_imag; __m128 real = _mm_setzero_ps();
// Simple iteration over every pixel in the picture __m128 imag = _mm_setzero_ps();
for(ssize_t y = 0; y < actual_height; y++){ __m128i iterations = _mm_setzero_si128();
// Calculate imaginary part on each row as it stays the same __m128i active_mask = _mm_set1_epi32(-1);
float c_real = c_start_real;
for(size_t x = 0; x < width; x++){
unsigned value = converge(c_real,c_imag,n);
// Pixel value position in the output array for(unsigned i = 0; i < n;i++) {
__m128 real2 = _mm_mul_ps(real,real);
__m128 imag2 = _mm_mul_ps(imag,imag);
if(!color){ // Grayscale colors as specified by the assignment r = g = b __m128 unbounded = _mm_cmple_ps(_mm_add_ps(real2,imag2),four);
img[position] = value; __m128i unbounded_i = _mm_castps_si128(unbounded);
active_mask = _mm_and_si128(active_mask,unbounded_i);
if(_mm_test_all_zeros(active_mask,active_mask)) {
break;
}
iterations = _mm_sub_epi32(iterations,active_mask);
imag = _mm_add_ps(c_imag_vector,_mm_mul_ps(minus_two, _mm_mul_ps(real,imag)));
real = _mm_add_ps(_mm_sub_ps(real2,imag2),c_real_vector);
} }
else{ // Temporary colors with random multipliers
__m128i mod64 = _mm_and_si128(iterations, _mm_set1_epi32(63));
__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 is_max = _mm_cmpeq_epi32(iterations, max_iter);
values = _mm_andnot_si128(is_max, values);
unsigned int value_array[4];
_mm_storeu_si128((__m128i*)value_array,values);
for(unsigned i = 0 ; i < 4;i++) {
unsigned int value = value_array[i];
size_t position = (y*width + (x+i)) * bytesPerColor;
if(!color) {
img[position] = value;
}
else {
img[position] = ((value*3) % 255);
img[position + 1] = ((value*2) % 255);
img[position + 2] = ((value*5) % 255);
}
}
}
for(;x < width; x++) {
float c_real = c_start_real + (x*res);
unsigned int value = converge(c_real,c_imag,n);
size_t position = (y*width + x) * bytesPerColor;
if(!color) {
img[position] = value;
}
else {
img[position] = ((value*3) % 255); img[position] = ((value*3) % 255);
img[position + 1] = ((value*2) % 255) ; img[position + 1] = ((value*2) % 255);
img[position + 2] = ((value*5) % 255); img[position + 2] = ((value*5) % 255);
} }
position += bytesPerColor;
c_real += res;
} }
c_imag += vertical_step;
} }
} }
// Iterative application of the equation given by the assignment static unsigned converge(float c_real, float c_imag, unsigned n) {
static inline unsigned converge(float c_real, float c_imag, unsigned n) {
float real = 0.0f; float real = 0.0f;
float imag = 0.0f; float imag = 0.0f;
@@ -73,5 +124,5 @@ static inline unsigned converge(float c_real, float c_imag, unsigned n) {
imag = -2.0f * real * imag + c_imag; imag = -2.0f * real * imag + c_imag;
real = real2 - imag2 + c_real; real = real2 - imag2 + c_real;
} }
return 0; return 0;
} }
+6 -5
View File
@@ -1,5 +1,6 @@
#include <complex.h> #include <complex.h>
#include <stddef.h> #include <stddef.h>
#include <math.h> #include <math.h>
#include <sys/types.h> #include <sys/types.h>
void tricorn_V2(float complex start, size_t width, ssize_t height, float res, unsigned n, _Bool color, unsigned char* img); #include<stdbool.h>
void tricorn_V2(float complex start, size_t width, ssize_t height, float res, unsigned n, bool color, unsigned char* img);
+122 -37
View File
@@ -1,11 +1,29 @@
#include "tricorn_V3.h" #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 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 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. * @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. * @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){ 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 // Absolute height for iteration purposes
ssize_t actual_height = height < 0 ? (ssize_t)(-height) : height; const size_t simd_width = (c->width / 4) * 4;
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;
float c_start_real = args->start_r;
__m128 four = _mm_set1_ps(4.0f); __m128 four = _mm_set1_ps(4.0f);
__m128 minus_two = _mm_set1_ps(-2.0f); __m128 minus_two = _mm_set1_ps(-2.0f);
for(ssize_t y = 0; y < actual_height;y++) { float c_imag = args->start_i;
float c_imag = c_start_imag + (y * imag_direction) * res; __m128 c_imag_vector = _mm_set1_ps(c_imag);
__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; 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 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 real = _mm_setzero_ps();
__m128 imag = _mm_setzero_ps(); __m128 imag = _mm_setzero_ps();
__m128i iterations = _mm_setzero_si128(); __m128i iterations = _mm_setzero_si128();
__m128i active_mask = _mm_set1_epi32(-1); __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 real2 = _mm_mul_ps(real,real);
__m128 imag2 = _mm_mul_ps(imag,imag); __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 times4 = _mm_slli_epi32(mod64, 2);
__m128i values = _mm_sub_epi32(_mm_set1_epi32(255), times4); __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); __m128i is_max = _mm_cmpeq_epi32(iterations, max_iter);
values = _mm_andnot_si128(is_max, values); 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++) { for(unsigned i = 0 ; i < 4;i++) {
unsigned int value = value_array[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) { if(!c->color) {
img[position] = value; c->img[position] = value;
} }
else { else {
img[position] = ((value*3) % 255); c->img[position] = ((value*3) % 255);
img[position + 1] = ((value*2) % 255); c->img[position + 1] = ((value*2) % 255);
img[position + 2] = ((value*5) % 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++) { for(;x < c->width; x++) {
float c_real = c_start_real + (x*res); float c_real = c_start_real + (x*c->res);
unsigned int value = converge(c_real,c_imag,n); 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) { if(!c->color) {
img[position] = value; c->img[position] = value;
} }
else { else {
img[position] = ((value*3) % 255); c->img[position] = ((value*3) % 255);
img[position + 1] = ((value*2) % 255); c->img[position + 1] = ((value*2) % 255);
img[position + 2] = ((value*5) % 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) { static unsigned converge(float c_real, float c_imag, unsigned n) {
float real = 0.0f; float real = 0.0f;
float imag = 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; real = real2 - imag2 + c_real;
} }
return 0; 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
} }
+13 -6
View File
@@ -1,6 +1,13 @@
#include <complex.h> #include <complex.h>
#include <stddef.h> #include <stddef.h>
#include <math.h> #include <math.h>
#include <sys/types.h> #include <sys/types.h>
#include<stdbool.h> #include <immintrin.h>
void tricorn_V3(float complex start, size_t width, ssize_t height, float res, unsigned n, bool color, unsigned char* img); #include <pthread.h>
#ifdef WIN32
#include <sysinfoapi.h>
#else
#include <unistd.h>
#endif
void tricorn_V3(float complex start, size_t width, ssize_t height, float res, unsigned n, _Bool color, unsigned char* img);
-132
View File
@@ -1,132 +0,0 @@
#include "tricorn_V4.h"
static unsigned converge(float c_real, float c_imag, unsigned n);
/**
* @brief Generates a Tricorn fractal image using an optimized SIMD algorithm
* @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.
* @param res The step size (resolution) per pixel in the complex plane.
* @param n The maximum number of mathematical iterations per pixel.
* @param color If true, generates a 24-bit RGB image. If false, writes a grayscale image.
* @param img Pointer to the pre-allocated memory buffer where the bitmap data will be stored.
*/
void tricorn_V4(float complex start, size_t width, ssize_t height, float res, unsigned n, _Bool color, unsigned char* img){
int bytesPerColor = 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;
__m128 four = _mm_set1_ps(4.0f);
__m128 minus_two = _mm_set1_ps(-2.0f);
float c_imag = c_start_imag;
__m128 c_imag_vector = _mm_set1_ps(c_imag);
float four_res = 4 * res;
float vertical_step = imag_direction * res;
__m128 vertical_step_vector = _mm_set1_ps(vertical_step);
for(ssize_t y = 0; y < actual_height;y++) {
size_t x = 0;
__m128 c_real_vector = _mm_setr_ps(c_start_real,c_start_real + 1.0f*res,c_start_real + 2.0f*res, c_start_real + 3.0f*res);
for(; x < simd_width;x += 4) { //SIMD Loop
__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++) {
__m128 real2 = _mm_mul_ps(real,real);
__m128 imag2 = _mm_mul_ps(imag,imag);
__m128 unbounded = _mm_cmple_ps(_mm_add_ps(real2,imag2),four);
__m128i unbounded_i = _mm_castps_si128(unbounded);
active_mask = _mm_and_si128(active_mask,unbounded_i);
if(_mm_test_all_zeros(active_mask,active_mask)) {
break;
}
iterations = _mm_sub_epi32(iterations,active_mask);
imag = _mm_add_ps(c_imag_vector,_mm_mul_ps(minus_two, _mm_mul_ps(real,imag)));
real = _mm_add_ps(_mm_sub_ps(real2,imag2),c_real_vector);
}
__m128i mod64 = _mm_and_si128(iterations, _mm_set1_epi32(63));
__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 is_max = _mm_cmpeq_epi32(iterations, max_iter);
values = _mm_andnot_si128(is_max, values);
unsigned int value_array[4];
_mm_storeu_si128((__m128i*)value_array,values);
for(unsigned i = 0 ; i < 4;i++) {
unsigned int value = value_array[i];
size_t position = (y*width + (x+i)) * bytesPerColor;
if(!color) {
img[position] = value;
}
else {
img[position] = ((value*3) % 255);
img[position + 1] = ((value*2) % 255);
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);
size_t position = (y*width + x) * bytesPerColor;
if(!color) {
img[position] = value;
}
else {
img[position] = ((value*3) % 255);
img[position + 1] = ((value*2) % 255);
img[position + 2] = ((value*5) % 255);
}
}
c_imag += vertical_step;
c_imag_vector = _mm_add_ps(c_imag_vector,vertical_step_vector);
}
}
static unsigned converge(float c_real, float c_imag, unsigned n) {
float real = 0.0f;
float imag = 0.0f;
for (unsigned i = 0; i < n; i++) {
float real2 = real * real;
float imag2 = imag * imag;
if (real2 + imag2 > 4.0f) {
return 255 - 4 * (i % 64);
}
imag = -2.0f * real * imag + c_imag;
real = real2 - imag2 + c_real;
}
return 0;
}
-7
View File
@@ -1,7 +0,0 @@
#include <complex.h>
#include <stddef.h>
#include <math.h>
#include <sys/types.h>
#include<stdbool.h>
#include <immintrin.h>
void tricorn_V4(float complex start, size_t width, ssize_t height, float res, unsigned n, bool color, unsigned char* img);
-13
View File
@@ -1,13 +0,0 @@
#include <complex.h>
#include <stddef.h>
#include <math.h>
#include <sys/types.h>
#include <immintrin.h>
#include <pthread.h>
#ifdef WIN32
#include <sysinfoapi.h>
#else
#include <unistd.h>
#endif
void tricorn_V5(float complex start, size_t width, ssize_t height, float res, unsigned n, _Bool color, unsigned char* img);