Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.zig-cache/
node_modules/
zig-out/
.benchtree/
160 changes: 160 additions & 0 deletions bench.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#!/usr/bin/env bash

# builds the last 8 commits and compares the performance of odiff
# against the current git HEAD

function print_help() {
echo "usage: ./bench.sh [-n <num>] [--unstaged] [--help]"
echo ""
echo "-c, --clean : cleans the build directory (default: false)"
echo "-s, --skip-build : skips building a commit if it already exists (default: true)"
echo "-f, --from <commit> : compares the changes from the given commit (default: HEAD)"
echo "-n <num> : number of commits to compare (default: 8)"
echo "-u, --unstaged : also builds and compares with unstaged changes (default: false)"
echo "-r, --runs <num> : number of runs (default: 10)"
echo "-h, --help : prints this help message"
}

# must be in sync with build.zig
exe_name="odiff"
function build_project() {
local id="$1"
echo "Building $id"
zig build -Doptimize=ReleaseFast --prefix-exe-dir "bench/$id"
if [[ $? -ne 0 ]]; then
echo "Failed to build $id"
return 1
fi
echo "Successfully built $id"
}

function checkout_and_build() {
local commit_id="$1"
local skip_if_exists="$2"

echo "Checking out $commit_id"
if [[ $commit_id != "unstaged" ]]; then
if [[ $skip_if_exists == true ]]; then
if [[ -d "zig-out/bench/$commit_id" ]]; then
echo "Skipping build of $commit_id"
return
fi
fi

if [[ ! -d ".benchtree" ]]; then
git worktree add ".benchtree" "$commit_id"
fi
pushd ".benchtree"
git checkout "$commit_id"
build_project "$commit_id"
if [[ $? -ne 0 ]]; then
popd
return 1
fi
popd
mkdir -p "zig-out/bench/$commit_id"
mv ".benchtree/zig-out/bench/$commit_id/$exe_name" "zig-out/bench/$commit_id/$exe_name"
echo "Successfully built zig-out/bench/$commit_id/$exe_name"
else
build_project "$commit_id"
fi
}

function get_commit_ids() {
local from="$1"
local commit_ids=()

if $unstaged && [[ -n $(git diff --name-only) ]]; then
commit_ids+=("unstaged")
fi

commit_ids+=($(git rev-parse "$from"))
if [[ $num_commits -gt 1 ]]; then
for i in $(seq 1 $(($num_commits-1))); do
commit_ids+=($(git rev-parse "$from"~"$i"))
done
fi
echo "${commit_ids[@]}"
}

args=$(getopt -o "n:uh" --long "unstaged,help,runs,from" -n "bench.sh" -- "$@")

num_commits=8
unstaged=false
clean=false
skip_if_exists=true
runs=10
from="HEAD"
while true; do
case "$1" in
-n)
num_commits="$2"
shift 2
;;
-c | --clean)
clean=true
shift
;;
-s | --skip-build)
skip_if_exists=true
shift
;;
-u | --unstaged)
unstaged=true
shift
;;
-r | --runs)
runs="$2"
shift 2
;;
-f | --from)
from="$2"
shift 2
;;
-h | --help)
print_help
exit 0
;;
--)
shift
break
;;
"")
break
;;
*)
print_help
exit 1
;;
esac
done

if $clean; then
echo "Cleaning output directory"
rm -rf "zig-out/bench"
fi

commit_ids=($(get_commit_ids "$from"))
runnable_commit_ids=()
echo "Building $num_commits commits"
for commit_id in "${commit_ids[@]}"; do
checkout_and_build "$commit_id" "$skip_if_exists"
if [[ $? -eq 0 ]]; then
runnable_commit_ids+=("$commit_id")
fi
done

# ensure that hyperfine is installed
if ! command -v hyperfine &> /dev/null; then
echo "hyperfine is not installed."
echo "Please install hyperfine using the following command:"
echo "cargo install hyperfine"
exit 1
fi

bench_cmd="hyperfine -i -N --warmup 1 --runs $runs "
options="./images/www.cypress.io.png ./images/www.cypress.io-1.png ./images/www.cypress-diff.png"
for commit_id in "${runnable_commit_ids[@]}"; do
bench_cmd+="\"$PWD/zig-out/bench/$commit_id/$exe_name $options\" "
done
eval "$bench_cmd"
6 changes: 1 addition & 5 deletions src/io/io.zig
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,9 @@ pub fn saveImageWithFormat(img: Image, file_path: []const u8, format: ImageForma
.truncate = true,
});
defer file.close();
var buffer: [1024 * 1024]u8 = undefined;
var file_writer = file.writer(&buffer);

switch (format) {
.png => try png.save(img, &file_writer.interface),
.png => try png.save(img, file),
else => return error.UnsupportedFormat,
}

try file_writer.interface.flush();
}
12 changes: 8 additions & 4 deletions src/io/png.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub fn load(allocator: std.mem.Allocator, data: []const u8) !Image {
};
}

pub fn save(img: Image, writer: *std.Io.Writer) !void {
pub fn save(img: Image, file: std.fs.File) !void {
const ctx = c.spng_ctx_new(c.SPNG_CTX_ENCODER) orelse return error.OutOfMemory;
defer c.spng_ctx_free(ctx);

Expand All @@ -53,6 +53,10 @@ pub fn save(img: Image, writer: *std.Io.Writer) !void {
};
if (c.spng_set_ihdr(ctx, &ihdr) != 0) return error.InvalidData;

if (c.spng_set_option(ctx, c.SPNG_FILTER_CHOICE, c.SPNG_DISABLE_FILTERING) != 0) return error.InvalidData;

var buffer: [4096]u8 = undefined;
var file_writer = file.writer(&buffer);
if (c.spng_set_png_stream(
ctx,
struct {
Expand All @@ -66,11 +70,11 @@ pub fn save(img: Image, writer: *std.Io.Writer) !void {
return 0;
}
}.writeFn,
@ptrCast(@alignCast(writer)),
@ptrCast(@alignCast(&file_writer.interface)),
) != 0) return error.InvalidData;

const pixel_data = img.slice();
const res = c.spng_encode_image(ctx, @ptrCast(@alignCast(pixel_data.ptr)), pixel_data.len * @sizeOf(u32), c.SPNG_FMT_PNG, c.SPNG_ENCODE_FINALIZE);
const u8_slice: []u8 = @ptrCast(img.slice());
const res = c.spng_encode_image(ctx, u8_slice.ptr, u8_slice.len, c.SPNG_FMT_PNG, c.SPNG_ENCODE_FINALIZE);
if (res != 0) {
const err_msg = std.mem.span(c.spng_strerror(res));
std.log.err("writePNG: failed to encode image {s}", .{err_msg});
Expand Down