Skip to content

Latest commit

 

History

21 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Grayscale Integer to Float Converter

Authors: Chiu, Lance & Gonzaga, Rainer

This project converts a grayscale image represented by an 8 bit unsigned integer array (0-255) into a single precision floating point array (0.0 - 1.0). The C program covers memory allocation, I/O, output validation, and benchmarking, while the core math conversion is executed using x86-64 assembly with scalar SIMD floating-point instructions.

Requirements

  • OS: Windows (64-bit)
  • Compiler: GCC v5.0+ (mingw-w64)
  • Assembler: NASM v2.14+
  • Build Tool: GNU Make v3.81+

Makefile

  • make: Compiles the C and Assembly source files and builds the executable.
  • make run: Builds and immediately runs the program.
  • make clean: Removes the build/ directory and all compiled object files.

Performance Analysis

To measure performance, both the x86-64 assembly routine and a plain C equivalent were each called 30 times per image size, with results averaged using QueryPerformanceCounter. Pixel values were generated randomly for each test.

Image size Pixel count C avg. time (30 runs) x86-64 asm avg. time (30 runs) Speedup
10 x 10 100 0.000000153 s 0.000000053 s 2.87x
100 x 100 10,000 0.000012050 s 0.000006640 s 1.81x
1000 x 1000 1,000,000 0.000802970 s 0.000674457 s 1.19x

For both implementations, execution time scales roughly with pixel count, which makes sense since each pixel goes through the same fixed set of operations with no branching involved. More pixels just means repeating that same cheap work more times.

The assembly version is faster than the C version at every size. One instruction we had to be careful with is cvtsi2ss xmm0, r9d, which only writes the lower 32 bits of xmm0 and leaves the rest of the register untouched. If xmm0 isn't cleared beforehand, the CPU can treat that as a dependency on its value from the previous loop iteration and stall waiting for it, even though the old value isn't actually used. We avoid this by zeroing xmm0 with xorps xmm0, xmm0 right before cvtsi2ss every iteration. XOR-ing a register with itself is a special case CPUs detect and treat as just setting it to zero, with no dependency on whatever value was there before. That keeps the loop running at full speed instead of stalling on a dependency that doesn't need to exist.

At the largest size (1000 x 1000), we think both versions are more limited by memory access than by computation, since each pixel is only 1 byte in and 4 bytes out, and the divide itself is cheap on modern hardware.

10 x 10 benchmark:

10x10 benchmark

100 x 100 benchmark:

100x100 benchmark

1000 x 1000 benchmark:

1000x1000 benchmark

Screenshot of Correctness Check

correctness check

Demo Video

You may find the demo video locally in public/demo_video.mp4, or you can view it on Google Drive.

About

an x86-to-C interface programming project

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages