From 62484f2e6043def45e9d50021450d923640c23d2 Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 16:12:14 +0100 Subject: [PATCH 01/14] WIP add Fortran benchmark utility --- lib/fortran/benchmark.f90 | 67 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 lib/fortran/benchmark.f90 diff --git a/lib/fortran/benchmark.f90 b/lib/fortran/benchmark.f90 new file mode 100644 index 00000000..fd539474 --- /dev/null +++ b/lib/fortran/benchmark.f90 @@ -0,0 +1,67 @@ +module benchmark + implicit none + private + public :: run, format_results, benchmark_result_t ! Make benchmark_result_t public + + type :: benchmark_result_t + integer :: runs + real(8) :: mean_ms + real(8) :: std_dev_ms + real(8) :: min_ms + real(8) :: max_ms + integer(8) :: result + end type benchmark_result_t + +contains + + subroutine run(f, run_ms, result) + implicit none + interface + integer(8) function f() + end function f + end interface + procedure(f), pointer :: func_ptr + integer, intent(in) :: run_ms + type(benchmark_result_t), intent(out) :: result + integer(8) :: start_time, end_time, elapsed_time, total_elapsed_time + integer :: count + real(8) :: elapsed_times(1000), mean, variance, std_dev, min_time, max_time + + func_ptr => f + total_elapsed_time = 0 + count = 0 + min_time = 1.0e12 + max_time = 0.0 + + do while (total_elapsed_time < run_ms * 1.0e6) + call system_clock(start_time) + result%result = func_ptr() + call system_clock(end_time) + elapsed_time = end_time - start_time + elapsed_times(count + 1) = elapsed_time / 1.0e6 + total_elapsed_time = total_elapsed_time + elapsed_time + count = count + 1 + if (elapsed_times(count) < min_time) min_time = elapsed_times(count) + if (elapsed_times(count) > max_time) max_time = elapsed_times(count) + end do + + mean = sum(elapsed_times(1:count)) / count + variance = sum((elapsed_times(1:count) - mean)**2) / count + std_dev = sqrt(variance) + + result%runs = count + result%mean_ms = mean + result%std_dev_ms = std_dev + result%min_ms = min_time + result%max_ms = max_time + end subroutine run + + subroutine format_results(benchmark_result, result_str) + implicit none + type(benchmark_result_t), intent(in) :: benchmark_result + character(len=256), intent(out) :: result_str + write(result_str, '(f6.3,1x,f6.3,1x,f6.3,1x,f6.3,1x,i0,1x,i8)') & + benchmark_result%mean_ms, benchmark_result%std_dev_ms, benchmark_result%min_ms, benchmark_result%max_ms, benchmark_result%runs, benchmark_result%result + end subroutine format_results + +end module benchmark From dfe735f9b3989d9ebed627f651d6445afda70bc4 Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 16:13:11 +0100 Subject: [PATCH 02/14] WIP: Add in-process Levenshtein for Fortran --- compile.sh | 2 + levenshtein/fortran/run.f90 | 189 ++++++++++++++++++++++++++++++++++++ run.sh | 1 + 3 files changed, 192 insertions(+) create mode 100644 levenshtein/fortran/run.f90 diff --git a/compile.sh b/compile.sh index 63711608..3dfc17cb 100755 --- a/compile.sh +++ b/compile.sh @@ -56,6 +56,8 @@ compile 'Clojure' 'clojure' '(cd clojure && mkdir -p classes && clojure -M -e "( compile 'Clojure Native' 'clojure-native-image' "(cd clojure-native-image ; clojure -M:native-image-run --pgo-instrument -march=native) ; ./clojure-native-image/run -XX:ProfilesDumpFile=clojure-native-image/run.iprof 10000 2000 $(./check-output.sh -i) && (cd clojure-native-image ; clojure -M:native-image-run --pgo=run.iprof -march=native)" compile 'Java' 'jvm' 'javac -cp ../lib/java jvm/run.java' compile 'Java Native' 'java-native-image' "(cd java-native-image ; native-image -cp ..:../../lib/java --no-fallback -O3 --pgo-instrument -march=native jvm.run) && ./java-native-image/jvm.run -XX:ProfilesDumpFile=java-native-image/run.iprof 10000 2000 $(./check-output.sh -i) && (cd java-native-image ; native-image -cp ..:../../lib/java -O3 --pgo=run.iprof -march=native jvm.run -o run)" +compile 'Fortran' 'fortran' 'gfortran -O3 -I../lib/fortran ../lib/fortran/benchmark.f90 fortran/run.f90 -o fortran/run' + ####### END The languages echo diff --git a/levenshtein/fortran/run.f90 b/levenshtein/fortran/run.f90 new file mode 100644 index 00000000..7e553ed1 --- /dev/null +++ b/levenshtein/fortran/run.f90 @@ -0,0 +1,189 @@ +program main + use benchmark + implicit none + + integer :: run_ms, warmup_ms, iostat, word_count + character(len=256) :: run_ms_str, warmup_ms_str, input_path + character(len=:), allocatable :: args(:) + integer, allocatable :: distances(:) + type(benchmark_result_t) :: warmup_result, benchmark_result + character(len=256) :: result_str + + call get_command_argument(1, run_ms_str) + call get_command_argument(2, warmup_ms_str) + call get_command_argument(3, input_path) + read(run_ms_str, *) run_ms + read(warmup_ms_str, *) warmup_ms + + call read_all_words(input_path, args, iostat) + if (iostat /= 0) stop "Error reading file." + word_count = size(args) + if (word_count == 0) stop "No words read." + + allocate(distances((word_count*(word_count-1))/2)) + call run(benchmark_function, warmup_ms, warmup_result) + call run(benchmark_function, run_ms, benchmark_result) + benchmark_result%result = sum(distances) + + call format_results(benchmark_result, result_str) + write(*,'(A)') result_str + + deallocate(args, distances) + +contains + + integer(8) function benchmark_function() + implicit none + integer :: i, j, idx + integer(8) :: sum_distances + sum_distances = 0 + idx = 1 + do i = 1, size(args) + do j = i + 1, size(args) + distances(idx) = levenshtein_distance(trim(args(i)), trim(args(j))) + sum_distances = sum_distances + distances(idx) + idx = idx + 1 + end do + end do + benchmark_function = sum_distances + end function benchmark_function + +subroutine read_all_words(filename, all_words, iostat) + implicit none + character(len=*), intent(in) :: filename + character(len=:), allocatable, intent(out) :: all_words(:) + integer, intent(out) :: iostat + integer :: unit, num_words, i + character(len=256) :: word + + open(newunit=unit, file=filename, status='old', action='read', iostat=iostat) + if (iostat /= 0) return + + num_words = 0 + do + read(unit, '(A)', iostat=iostat) word + if (iostat /= 0) exit + num_words = num_words + 1 + end do + + if (num_words > 0) then + allocate(character(len=256) :: all_words(num_words)) + rewind(unit) + do i = 1, num_words + read(unit, '(A)', iostat=iostat) all_words(i) + if (iostat /= 0) exit + end do + else + allocate(all_words, mold=[character(len=256) :: '']) + end if + + close(unit) +end subroutine read_all_words + + subroutine split(string, delimiter, parts) + character(len=*), intent(in) :: string + character(len=*), intent(in) :: delimiter + character(len=*), intent(out) :: parts(:) + integer :: i, start, finish, p + start = 1 + p = 1 + do i = 1, len(string) + if (string(i:i) == delimiter) then + finish = i - 1 + if (finish < start) then + parts(p) = '' + else + parts(p) = string(start:finish) + end if + p = p + 1 + start = i + 1 + end if + end do + if (start <= len(string)) then + parts(p) = string(start:) + else + parts(p) = '' + end if + end subroutine split + + ! Calculates the Levenshtein distance between two strings using Wagner-Fischer algorithm + ! Space Complexity: O(min(m,n)) - only uses two arrays instead of full matrix + ! Time Complexity: O(m*n) where m and n are the lengths of the input strings + function levenshtein_distance(s1, s2) result(distance) + character(len=*), intent(in) :: s1, s2 + integer :: distance + + integer :: m, n, i, j, cost + integer, allocatable :: prev_row(:), curr_row(:) + character(len=1) :: c1, c2 + character(len=:), allocatable :: str1, str2 + + ! Early termination checks + if (s1 == s2) then + distance = 0 + return + end if + + if (len_trim(s1) == 0) then + distance = len_trim(s2) + return + end if + + if (len_trim(s2) == 0) then + distance = len_trim(s1) + return + end if + + ! Make s1 the shorter string for space optimization + if (len_trim(s1) > len_trim(s2)) then + str1 = trim(s2) + str2 = trim(s1) + else + str1 = trim(s1) + str2 = trim(s2) + end if + + m = len_trim(str1) + n = len_trim(str2) + + ! Use two arrays instead of full matrix for space optimization + allocate(prev_row(0:m), curr_row(0:m)) + + ! Initialize first row + do i = 0, m + prev_row(i) = i + end do + + ! Main computation loop + do j = 1, n + curr_row(0) = j + + do i = 1, m + ! Get characters at current position + c1 = str1(i:i) + c2 = str2(j:j) + + ! Calculate cost + if (c1 == c2) then + cost = 0 + else + cost = 1 + end if + + ! Calculate minimum of three operations + curr_row(i) = min(prev_row(i) + 1, & ! deletion + curr_row(i-1) + 1, & ! insertion + prev_row(i-1) + cost) ! substitution + end do + + ! Swap rows + prev_row = curr_row + end do + + distance = prev_row(m) + + ! Clean up + deallocate(prev_row, curr_row) + end function levenshtein_distance + +end program main diff --git a/run.sh b/run.sh index 068dc178..296f1535 100755 --- a/run.sh +++ b/run.sh @@ -154,6 +154,7 @@ run "Babashka" "bb/run.clj" "bb bb/run.clj" run "C" "./c/run" "./c/run" run "Clojure" "./clojure/classes/run.class" "java -cp clojure/classes:$(clojure -Spath) run" run "Clojure Native" "./clojure-native-image/run" "./clojure-native-image/run" +run "Fortran" "./fortran/run" "./fortran/run" run "Java" "./jvm/run.class" "java -cp .:../lib/java jvm.run" run "Java Native" "./java-native-image/run" "./java-native-image/run" ####### END The languages From d2732b1fe84e16212fe8a16ebc44b4dbbc8096e8 Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 16:37:21 +0100 Subject: [PATCH 03/14] Fortran levenshtein allow for 10K long words --- levenshtein/fortran/run.f90 | 181 ++++++++++++++++++------------------ 1 file changed, 91 insertions(+), 90 deletions(-) diff --git a/levenshtein/fortran/run.f90 b/levenshtein/fortran/run.f90 index 7e553ed1..997385fe 100644 --- a/levenshtein/fortran/run.f90 +++ b/levenshtein/fortran/run.f90 @@ -3,11 +3,11 @@ program main implicit none integer :: run_ms, warmup_ms, iostat, word_count - character(len=256) :: run_ms_str, warmup_ms_str, input_path - character(len=:), allocatable :: args(:) + character(len = 256) :: run_ms_str, warmup_ms_str, input_path + character(len = :), allocatable :: args(:) integer, allocatable :: distances(:) type(benchmark_result_t) :: warmup_result, benchmark_result - character(len=256) :: result_str + character(len = 256) :: result_str call get_command_argument(1, run_ms_str) call get_command_argument(2, warmup_ms_str) @@ -20,19 +20,19 @@ program main word_count = size(args) if (word_count == 0) stop "No words read." - allocate(distances((word_count*(word_count-1))/2)) + allocate(distances((word_count * (word_count - 1)) / 2)) call run(benchmark_function, warmup_ms, warmup_result) call run(benchmark_function, run_ms, benchmark_result) benchmark_result%result = sum(distances) call format_results(benchmark_result, result_str) - write(*,'(A)') result_str + write(*, '(A)') result_str deallocate(args, distances) -contains + contains - integer(8) function benchmark_function() + integer(8) function benchmark_function() implicit none integer :: i, j, idx integer(8) :: sum_distances @@ -48,42 +48,43 @@ integer(8) function benchmark_function() benchmark_function = sum_distances end function benchmark_function -subroutine read_all_words(filename, all_words, iostat) - implicit none - character(len=*), intent(in) :: filename - character(len=:), allocatable, intent(out) :: all_words(:) - integer, intent(out) :: iostat - integer :: unit, num_words, i - character(len=256) :: word - - open(newunit=unit, file=filename, status='old', action='read', iostat=iostat) - if (iostat /= 0) return - - num_words = 0 - do - read(unit, '(A)', iostat=iostat) word - if (iostat /= 0) exit - num_words = num_words + 1 - end do - - if (num_words > 0) then - allocate(character(len=256) :: all_words(num_words)) - rewind(unit) - do i = 1, num_words - read(unit, '(A)', iostat=iostat) all_words(i) + subroutine read_all_words(filename, all_words, iostat) + implicit none + character(len = *), intent(in) :: filename + character(len = :), allocatable, intent(out) :: all_words(:) + integer, intent(out) :: iostat + integer :: unit, num_words, i + integer, parameter :: max_len = 10000 ! Allow for really long words + character(len = max_len) :: word + + open(newunit = unit, file = filename, status = 'old', action = 'read', iostat = iostat) + if (iostat /= 0) return + + num_words = 0 + do + read(unit, '(A)', iostat = iostat) word if (iostat /= 0) exit + num_words = num_words + 1 end do - else - allocate(all_words, mold=[character(len=256) :: '']) - end if - close(unit) -end subroutine read_all_words + if (num_words > 0) then + allocate(character(len = max_len) :: all_words(num_words)) + rewind(unit) + do i = 1, num_words + read(unit, '(A)', iostat = iostat) all_words(i) + if (iostat /= 0) exit + end do + else + allocate(all_words, mold = [character(len = max_len) :: '']) + end if + + close(unit) + end subroutine read_all_words subroutine split(string, delimiter, parts) - character(len=*), intent(in) :: string - character(len=*), intent(in) :: delimiter - character(len=*), intent(out) :: parts(:) + character(len = *), intent(in) :: string + character(len = *), intent(in) :: delimiter + character(len = *), intent(out) :: parts(:) integer :: i, start, finish, p start = 1 p = 1 @@ -106,84 +107,84 @@ subroutine split(string, delimiter, parts) end if end subroutine split - ! Calculates the Levenshtein distance between two strings using Wagner-Fischer algorithm - ! Space Complexity: O(min(m,n)) - only uses two arrays instead of full matrix - ! Time Complexity: O(m*n) where m and n are the lengths of the input strings - function levenshtein_distance(s1, s2) result(distance) - character(len=*), intent(in) :: s1, s2 + ! Calculates the Levenshtein distance between two strings using Wagner-Fischer algorithm + ! Space Complexity: O(min(m,n)) - only uses two arrays instead of full matrix + ! Time Complexity: O(m*n) where m and n are the lengths of the input strings + function levenshtein_distance(s1, s2) result(distance) + character(len = *), intent(in) :: s1, s2 integer :: distance - + integer :: m, n, i, j, cost integer, allocatable :: prev_row(:), curr_row(:) - character(len=1) :: c1, c2 - character(len=:), allocatable :: str1, str2 - + character(len = 1) :: c1, c2 + character(len = :), allocatable :: str1, str2 + ! Early termination checks if (s1 == s2) then - distance = 0 - return + distance = 0 + return end if - + if (len_trim(s1) == 0) then - distance = len_trim(s2) - return + distance = len_trim(s2) + return end if - + if (len_trim(s2) == 0) then - distance = len_trim(s1) - return + distance = len_trim(s1) + return end if - + ! Make s1 the shorter string for space optimization if (len_trim(s1) > len_trim(s2)) then - str1 = trim(s2) - str2 = trim(s1) + str1 = trim(s2) + str2 = trim(s1) else - str1 = trim(s1) - str2 = trim(s2) + str1 = trim(s1) + str2 = trim(s2) end if - + m = len_trim(str1) n = len_trim(str2) - + ! Use two arrays instead of full matrix for space optimization allocate(prev_row(0:m), curr_row(0:m)) - + ! Initialize first row do i = 0, m - prev_row(i) = i + prev_row(i) = i end do - + ! Main computation loop do j = 1, n - curr_row(0) = j - - do i = 1, m - ! Get characters at current position - c1 = str1(i:i) - c2 = str2(j:j) - - ! Calculate cost - if (c1 == c2) then - cost = 0 - else - cost = 1 - end if - - ! Calculate minimum of three operations - curr_row(i) = min(prev_row(i) + 1, & ! deletion - curr_row(i-1) + 1, & ! insertion - prev_row(i-1) + cost) ! substitution - end do - - ! Swap rows - prev_row = curr_row + curr_row(0) = j + + do i = 1, m + ! Get characters at current position + c1 = str1(i:i) + c2 = str2(j:j) + + ! Calculate cost + if (c1 == c2) then + cost = 0 + else + cost = 1 + end if + + ! Calculate minimum of three operations + curr_row(i) = min(prev_row(i) + 1, & ! deletion + curr_row(i - 1) + 1, & ! insertion + prev_row(i - 1) + cost) ! substitution + end do + + ! Swap rows + prev_row = curr_row end do - + distance = prev_row(m) - + ! Clean up deallocate(prev_row, curr_row) - end function levenshtein_distance + end function levenshtein_distance end program main From 2fdbfd8739ca135b7e946e847d18cfea4584765b Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 16:58:36 +0100 Subject: [PATCH 04/14] Fortran Benchmark utility: Add status printing to stderr, special cases, and periodic dots in Fortran benchmark --- lib/fortran/benchmark.f90 | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/fortran/benchmark.f90 b/lib/fortran/benchmark.f90 index fd539474..b2d62d91 100644 --- a/lib/fortran/benchmark.f90 +++ b/lib/fortran/benchmark.f90 @@ -26,12 +26,32 @@ end function f integer(8) :: start_time, end_time, elapsed_time, total_elapsed_time integer :: count real(8) :: elapsed_times(1000), mean, variance, std_dev, min_time, max_time + logical :: print_status + integer(8) :: last_status_t + + ! Check for run_ms being zero + if (run_ms == 0) then + result%runs = 0 + result%mean_ms = 0.0 + result%std_dev_ms = 0.0 + result%min_ms = 0.0 + result%max_ms = 0.0 + result%result = 0 + return + end if func_ptr => f total_elapsed_time = 0 count = 0 min_time = 1.0e12 max_time = 0.0 + print_status = (run_ms > 1) + call system_clock(last_status_t) + + if (print_status) then + write(*, '(A)', advance='no') "." + flush(6) + end if do while (total_elapsed_time < run_ms * 1.0e6) call system_clock(start_time) @@ -43,8 +63,17 @@ end function f count = count + 1 if (elapsed_times(count) < min_time) min_time = elapsed_times(count) if (elapsed_times(count) > max_time) max_time = elapsed_times(count) + if (print_status .and. (end_time - last_status_t) > 1000000000) then + last_status_t = end_time + write(*, '(A)', advance='no') "." + flush(6) + end if end do + if (print_status) then + write(*, '(A)') "" + end if + mean = sum(elapsed_times(1:count)) / count variance = sum((elapsed_times(1:count) - mean)**2) / count std_dev = sqrt(variance) @@ -64,4 +93,4 @@ subroutine format_results(benchmark_result, result_str) benchmark_result%mean_ms, benchmark_result%std_dev_ms, benchmark_result%min_ms, benchmark_result%max_ms, benchmark_result%runs, benchmark_result%result end subroutine format_results -end module benchmark +end module benchmark \ No newline at end of file From a1c00804cec1618a55fcdf814f8e20950a6680ca Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 17:25:52 +0100 Subject: [PATCH 05/14] Fortran levenshtein: Make output comma separated and trim the string --- levenshtein/fortran/run.f90 | 4 ++-- lib/fortran/benchmark.f90 | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/levenshtein/fortran/run.f90 b/levenshtein/fortran/run.f90 index 997385fe..e6d2298c 100644 --- a/levenshtein/fortran/run.f90 +++ b/levenshtein/fortran/run.f90 @@ -7,7 +7,7 @@ program main character(len = :), allocatable :: args(:) integer, allocatable :: distances(:) type(benchmark_result_t) :: warmup_result, benchmark_result - character(len = 256) :: result_str + character(len = :), allocatable :: result_str call get_command_argument(1, run_ms_str) call get_command_argument(2, warmup_ms_str) @@ -26,7 +26,7 @@ program main benchmark_result%result = sum(distances) call format_results(benchmark_result, result_str) - write(*, '(A)') result_str + write(*, '(A)') trim(adjustl(result_str)) deallocate(args, distances) diff --git a/lib/fortran/benchmark.f90 b/lib/fortran/benchmark.f90 index b2d62d91..bef0ced4 100644 --- a/lib/fortran/benchmark.f90 +++ b/lib/fortran/benchmark.f90 @@ -49,8 +49,8 @@ end function f call system_clock(last_status_t) if (print_status) then - write(*, '(A)', advance='no') "." - flush(6) + write(0, '(A)', advance='no') "." + flush(0) end if do while (total_elapsed_time < run_ms * 1.0e6) @@ -65,13 +65,13 @@ end function f if (elapsed_times(count) > max_time) max_time = elapsed_times(count) if (print_status .and. (end_time - last_status_t) > 1000000000) then last_status_t = end_time - write(*, '(A)', advance='no') "." - flush(6) + write(0, '(A)', advance='no') "." + flush(0) end if end do if (print_status) then - write(*, '(A)') "" + write(0, '(A)') "" end if mean = sum(elapsed_times(1:count)) / count @@ -86,11 +86,15 @@ end function f end subroutine run subroutine format_results(benchmark_result, result_str) - implicit none - type(benchmark_result_t), intent(in) :: benchmark_result - character(len=256), intent(out) :: result_str - write(result_str, '(f6.3,1x,f6.3,1x,f6.3,1x,f6.3,1x,i0,1x,i8)') & - benchmark_result%mean_ms, benchmark_result%std_dev_ms, benchmark_result%min_ms, benchmark_result%max_ms, benchmark_result%runs, benchmark_result%result + implicit none + type(benchmark_result_t), intent(in) :: benchmark_result + character(len=:), allocatable, intent(out) :: result_str + character(len=256) :: temp_str + + write(temp_str, '(f0.6,",",f0.6,",",f0.6,",",f0.6,",",i0,",",i0)') & + benchmark_result%mean_ms, benchmark_result%std_dev_ms, benchmark_result%min_ms, benchmark_result%max_ms, benchmark_result%runs, benchmark_result%result + + result_str = trim(adjustl(temp_str)) end subroutine format_results end module benchmark \ No newline at end of file From 80e1aa18d7f08bd9175c43642b3bf96088226a31 Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 17:37:27 +0100 Subject: [PATCH 06/14] Fortran levenshtein: Remove unused split function --- levenshtein/fortran/run.f90 | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/levenshtein/fortran/run.f90 b/levenshtein/fortran/run.f90 index e6d2298c..85c1d880 100644 --- a/levenshtein/fortran/run.f90 +++ b/levenshtein/fortran/run.f90 @@ -81,32 +81,6 @@ subroutine read_all_words(filename, all_words, iostat) close(unit) end subroutine read_all_words - subroutine split(string, delimiter, parts) - character(len = *), intent(in) :: string - character(len = *), intent(in) :: delimiter - character(len = *), intent(out) :: parts(:) - integer :: i, start, finish, p - start = 1 - p = 1 - do i = 1, len(string) - if (string(i:i) == delimiter) then - finish = i - 1 - if (finish < start) then - parts(p) = '' - else - parts(p) = string(start:finish) - end if - p = p + 1 - start = i + 1 - end if - end do - if (start <= len(string)) then - parts(p) = string(start:) - else - parts(p) = '' - end if - end subroutine split - ! Calculates the Levenshtein distance between two strings using Wagner-Fischer algorithm ! Space Complexity: O(min(m,n)) - only uses two arrays instead of full matrix ! Time Complexity: O(m*n) where m and n are the lengths of the input strings From dbc0a91e1c0265328f279d8f4438cc9c23bf7ee9 Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 17:55:41 +0100 Subject: [PATCH 07/14] Fortran levenshtein: Make benchmark.mod build file be generated in lib/fortran --- .gitignore | 2 ++ compile.sh | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ed91e6de..82d1f784 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ levenshtein/*/run hello-world/*/code hello-world/*/run .nrepl-port + +lib/fortran/benchmark.mod diff --git a/compile.sh b/compile.sh index 3dfc17cb..e8e6216e 100755 --- a/compile.sh +++ b/compile.sh @@ -56,7 +56,7 @@ compile 'Clojure' 'clojure' '(cd clojure && mkdir -p classes && clojure -M -e "( compile 'Clojure Native' 'clojure-native-image' "(cd clojure-native-image ; clojure -M:native-image-run --pgo-instrument -march=native) ; ./clojure-native-image/run -XX:ProfilesDumpFile=clojure-native-image/run.iprof 10000 2000 $(./check-output.sh -i) && (cd clojure-native-image ; clojure -M:native-image-run --pgo=run.iprof -march=native)" compile 'Java' 'jvm' 'javac -cp ../lib/java jvm/run.java' compile 'Java Native' 'java-native-image' "(cd java-native-image ; native-image -cp ..:../../lib/java --no-fallback -O3 --pgo-instrument -march=native jvm.run) && ./java-native-image/jvm.run -XX:ProfilesDumpFile=java-native-image/run.iprof 10000 2000 $(./check-output.sh -i) && (cd java-native-image ; native-image -cp ..:../../lib/java -O3 --pgo=run.iprof -march=native jvm.run -o run)" -compile 'Fortran' 'fortran' 'gfortran -O3 -I../lib/fortran ../lib/fortran/benchmark.f90 fortran/run.f90 -o fortran/run' +compile 'Fortran' 'fortran' "gfortran -O3 -J../lib/fortran ../lib/fortran/benchmark.f90 fortran/run.f90 -o fortran/run" ####### END The languages From 3c1e6c1039bf314e5a5e236811e3541d6f20a1bb Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 18:08:07 +0100 Subject: [PATCH 08/14] Clojure levenshtein: Update RCF --- levenshtein/clojure/run.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/levenshtein/clojure/run.clj b/levenshtein/clojure/run.clj index bd073630..8e50184c 100644 --- a/levenshtein/clojure/run.clj +++ b/levenshtein/clojure/run.clj @@ -57,6 +57,6 @@ println))) (comment - (-main "1000" "levenshtein/levenshtein-words.txt") + (-main "2000" "1000" "levenshtein-words.txt") :rcf) From ee3f33a7eebd0ddff5e9fdfbae3a81d4f1bd5ca4 Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 18:11:06 +0100 Subject: [PATCH 09/14] Fortran hello-world: Add run.f90 (copy of code.f90) --- hello-world/fortran/run.f90 | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 hello-world/fortran/run.f90 diff --git a/hello-world/fortran/run.f90 b/hello-world/fortran/run.f90 new file mode 100644 index 00000000..81ad01e6 --- /dev/null +++ b/hello-world/fortran/run.f90 @@ -0,0 +1,3 @@ +program main + print *, "Hello, World!" +end program main \ No newline at end of file From 2168e83ff308e8460c9609a1630e5cec2eff90dd Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 21:24:34 +0100 Subject: [PATCH 10/14] Fortran levenshtein: Make benched function return a list of distances --- levenshtein/fortran/run.f90 | 244 ++++++++++++++++++------------------ 1 file changed, 123 insertions(+), 121 deletions(-) diff --git a/levenshtein/fortran/run.f90 b/levenshtein/fortran/run.f90 index 85c1d880..a5ecd769 100644 --- a/levenshtein/fortran/run.f90 +++ b/levenshtein/fortran/run.f90 @@ -23,6 +23,8 @@ program main allocate(distances((word_count * (word_count - 1)) / 2)) call run(benchmark_function, warmup_ms, warmup_result) call run(benchmark_function, run_ms, benchmark_result) + + ! Sum the distances outside the benchmarked function benchmark_result%result = sum(distances) call format_results(benchmark_result, result_str) @@ -33,132 +35,132 @@ program main contains integer(8) function benchmark_function() - implicit none - integer :: i, j, idx - integer(8) :: sum_distances - sum_distances = 0 - idx = 1 - do i = 1, size(args) - do j = i + 1, size(args) - distances(idx) = levenshtein_distance(trim(args(i)), trim(args(j))) - sum_distances = sum_distances + distances(idx) - idx = idx + 1 + implicit none + integer :: i, j, idx + integer(8) :: sum_distances + sum_distances = 0 + idx = 1 + do i = 1, size(args) + do j = i + 1, size(args) + distances(idx) = levenshtein_distance(trim(args(i)), trim(args(j))) + sum_distances = sum_distances + distances(idx) + idx = idx + 1 + end do end do - end do - benchmark_function = sum_distances - end function benchmark_function - - subroutine read_all_words(filename, all_words, iostat) - implicit none - character(len = *), intent(in) :: filename - character(len = :), allocatable, intent(out) :: all_words(:) - integer, intent(out) :: iostat - integer :: unit, num_words, i - integer, parameter :: max_len = 10000 ! Allow for really long words - character(len = max_len) :: word - - open(newunit = unit, file = filename, status = 'old', action = 'read', iostat = iostat) - if (iostat /= 0) return - - num_words = 0 - do - read(unit, '(A)', iostat = iostat) word - if (iostat /= 0) exit - num_words = num_words + 1 - end do - - if (num_words > 0) then - allocate(character(len = max_len) :: all_words(num_words)) - rewind(unit) - do i = 1, num_words - read(unit, '(A)', iostat = iostat) all_words(i) + benchmark_function = sum_distances + end function benchmark_function + + subroutine read_all_words(filename, all_words, iostat) + implicit none + character(len = *), intent(in) :: filename + character(len = :), allocatable, intent(out) :: all_words(:) + integer, intent(out) :: iostat + integer :: unit, num_words, i + integer, parameter :: max_len = 10000 ! Allow for really long words + character(len = max_len) :: word + + open(newunit = unit, file = filename, status = 'old', action = 'read', iostat = iostat) + if (iostat /= 0) return + + num_words = 0 + do + read(unit, '(A)', iostat = iostat) word if (iostat /= 0) exit + num_words = num_words + 1 end do - else - allocate(all_words, mold = [character(len = max_len) :: '']) - end if - - close(unit) - end subroutine read_all_words - - ! Calculates the Levenshtein distance between two strings using Wagner-Fischer algorithm - ! Space Complexity: O(min(m,n)) - only uses two arrays instead of full matrix - ! Time Complexity: O(m*n) where m and n are the lengths of the input strings - function levenshtein_distance(s1, s2) result(distance) - character(len = *), intent(in) :: s1, s2 - integer :: distance - - integer :: m, n, i, j, cost - integer, allocatable :: prev_row(:), curr_row(:) - character(len = 1) :: c1, c2 - character(len = :), allocatable :: str1, str2 - - ! Early termination checks - if (s1 == s2) then - distance = 0 - return - end if - - if (len_trim(s1) == 0) then - distance = len_trim(s2) - return - end if - - if (len_trim(s2) == 0) then - distance = len_trim(s1) - return - end if - - ! Make s1 the shorter string for space optimization - if (len_trim(s1) > len_trim(s2)) then - str1 = trim(s2) - str2 = trim(s1) - else - str1 = trim(s1) - str2 = trim(s2) - end if - - m = len_trim(str1) - n = len_trim(str2) - - ! Use two arrays instead of full matrix for space optimization - allocate(prev_row(0:m), curr_row(0:m)) - - ! Initialize first row - do i = 0, m - prev_row(i) = i - end do - - ! Main computation loop - do j = 1, n - curr_row(0) = j - - do i = 1, m - ! Get characters at current position - c1 = str1(i:i) - c2 = str2(j:j) - - ! Calculate cost - if (c1 == c2) then - cost = 0 - else - cost = 1 - end if - - ! Calculate minimum of three operations - curr_row(i) = min(prev_row(i) + 1, & ! deletion - curr_row(i - 1) + 1, & ! insertion - prev_row(i - 1) + cost) ! substitution + + if (num_words > 0) then + allocate(character(len = max_len) :: all_words(num_words)) + rewind(unit) + do i = 1, num_words + read(unit, '(A)', iostat = iostat) all_words(i) + if (iostat /= 0) exit + end do + else + allocate(all_words, mold = [character(len = max_len) :: '']) + end if + + close(unit) + end subroutine read_all_words + + ! Calculates the Levenshtein distance between two strings using Wagner-Fischer algorithm + ! Space Complexity: O(min(m,n)) - only uses two arrays instead of full matrix + ! Time Complexity: O(m*n) where m and n are the lengths of the input strings + function levenshtein_distance(s1, s2) result(distance) + character(len = *), intent(in) :: s1, s2 + integer :: distance + + integer :: m, n, i, j, cost + integer, allocatable :: prev_row(:), curr_row(:) + character(len = 1) :: c1, c2 + character(len = :), allocatable :: str1, str2 + + ! Early termination checks + if (s1 == s2) then + distance = 0 + return + end if + + if (len_trim(s1) == 0) then + distance = len_trim(s2) + return + end if + + if (len_trim(s2) == 0) then + distance = len_trim(s1) + return + end if + + ! Make s1 the shorter string for space optimization + if (len_trim(s1) > len_trim(s2)) then + str1 = trim(s2) + str2 = trim(s1) + else + str1 = trim(s1) + str2 = trim(s2) + end if + + m = len_trim(str1) + n = len_trim(str2) + + ! Use two arrays instead of full matrix for space optimization + allocate(prev_row(0:m), curr_row(0:m)) + + ! Initialize first row + do i = 0, m + prev_row(i) = i end do - ! Swap rows - prev_row = curr_row - end do + ! Main computation loop + do j = 1, n + curr_row(0) = j + + do i = 1, m + ! Get characters at current position + c1 = str1(i:i) + c2 = str2(j:j) + + ! Calculate cost + if (c1 == c2) then + cost = 0 + else + cost = 1 + end if + + ! Calculate minimum of three operations + curr_row(i) = min(prev_row(i) + 1, & ! deletion + curr_row(i - 1) + 1, & ! insertion + prev_row(i - 1) + cost) ! substitution + end do + + ! Swap rows + prev_row = curr_row + end do - distance = prev_row(m) + distance = prev_row(m) - ! Clean up - deallocate(prev_row, curr_row) - end function levenshtein_distance + ! Clean up + deallocate(prev_row, curr_row) + end function levenshtein_distance -end program main + end program main \ No newline at end of file From 1312a9a685a814f0bd9fc9a2ed7ba0959a100081 Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 21:26:28 +0100 Subject: [PATCH 11/14] Fortran levenshtein: Format --- levenshtein/fortran/run.f90 | 242 ++++++++++++++++++------------------ 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/levenshtein/fortran/run.f90 b/levenshtein/fortran/run.f90 index a5ecd769..8805faab 100644 --- a/levenshtein/fortran/run.f90 +++ b/levenshtein/fortran/run.f90 @@ -35,132 +35,132 @@ program main contains integer(8) function benchmark_function() - implicit none - integer :: i, j, idx - integer(8) :: sum_distances - sum_distances = 0 - idx = 1 - do i = 1, size(args) - do j = i + 1, size(args) - distances(idx) = levenshtein_distance(trim(args(i)), trim(args(j))) - sum_distances = sum_distances + distances(idx) - idx = idx + 1 - end do + implicit none + integer :: i, j, idx + integer(8) :: sum_distances + sum_distances = 0 + idx = 1 + do i = 1, size(args) + do j = i + 1, size(args) + distances(idx) = levenshtein_distance(trim(args(i)), trim(args(j))) + sum_distances = sum_distances + distances(idx) + idx = idx + 1 end do - benchmark_function = sum_distances - end function benchmark_function - - subroutine read_all_words(filename, all_words, iostat) - implicit none - character(len = *), intent(in) :: filename - character(len = :), allocatable, intent(out) :: all_words(:) - integer, intent(out) :: iostat - integer :: unit, num_words, i - integer, parameter :: max_len = 10000 ! Allow for really long words - character(len = max_len) :: word - - open(newunit = unit, file = filename, status = 'old', action = 'read', iostat = iostat) - if (iostat /= 0) return - - num_words = 0 - do - read(unit, '(A)', iostat = iostat) word + end do + benchmark_function = sum_distances + end function benchmark_function + + subroutine read_all_words(filename, all_words, iostat) + implicit none + character(len = *), intent(in) :: filename + character(len = :), allocatable, intent(out) :: all_words(:) + integer, intent(out) :: iostat + integer :: unit, num_words, i + integer, parameter :: max_len = 10000 ! Allow for really long words + character(len = max_len) :: word + + open(newunit = unit, file = filename, status = 'old', action = 'read', iostat = iostat) + if (iostat /= 0) return + + num_words = 0 + do + read(unit, '(A)', iostat = iostat) word + if (iostat /= 0) exit + num_words = num_words + 1 + end do + + if (num_words > 0) then + allocate(character(len = max_len) :: all_words(num_words)) + rewind(unit) + do i = 1, num_words + read(unit, '(A)', iostat = iostat) all_words(i) if (iostat /= 0) exit - num_words = num_words + 1 end do - - if (num_words > 0) then - allocate(character(len = max_len) :: all_words(num_words)) - rewind(unit) - do i = 1, num_words - read(unit, '(A)', iostat = iostat) all_words(i) - if (iostat /= 0) exit - end do - else - allocate(all_words, mold = [character(len = max_len) :: '']) - end if - - close(unit) - end subroutine read_all_words - - ! Calculates the Levenshtein distance between two strings using Wagner-Fischer algorithm - ! Space Complexity: O(min(m,n)) - only uses two arrays instead of full matrix - ! Time Complexity: O(m*n) where m and n are the lengths of the input strings - function levenshtein_distance(s1, s2) result(distance) - character(len = *), intent(in) :: s1, s2 - integer :: distance - - integer :: m, n, i, j, cost - integer, allocatable :: prev_row(:), curr_row(:) - character(len = 1) :: c1, c2 - character(len = :), allocatable :: str1, str2 - - ! Early termination checks - if (s1 == s2) then - distance = 0 - return - end if - - if (len_trim(s1) == 0) then - distance = len_trim(s2) - return - end if - - if (len_trim(s2) == 0) then - distance = len_trim(s1) - return - end if - - ! Make s1 the shorter string for space optimization - if (len_trim(s1) > len_trim(s2)) then - str1 = trim(s2) - str2 = trim(s1) - else - str1 = trim(s1) - str2 = trim(s2) - end if - - m = len_trim(str1) - n = len_trim(str2) - - ! Use two arrays instead of full matrix for space optimization - allocate(prev_row(0:m), curr_row(0:m)) - - ! Initialize first row - do i = 0, m - prev_row(i) = i + else + allocate(all_words, mold = [character(len = max_len) :: '']) + end if + + close(unit) + end subroutine read_all_words + + ! Calculates the Levenshtein distance between two strings using Wagner-Fischer algorithm + ! Space Complexity: O(min(m,n)) - only uses two arrays instead of full matrix + ! Time Complexity: O(m*n) where m and n are the lengths of the input strings + function levenshtein_distance(s1, s2) result(distance) + character(len = *), intent(in) :: s1, s2 + integer :: distance + + integer :: m, n, i, j, cost + integer, allocatable :: prev_row(:), curr_row(:) + character(len = 1) :: c1, c2 + character(len = :), allocatable :: str1, str2 + + ! Early termination checks + if (s1 == s2) then + distance = 0 + return + end if + + if (len_trim(s1) == 0) then + distance = len_trim(s2) + return + end if + + if (len_trim(s2) == 0) then + distance = len_trim(s1) + return + end if + + ! Make s1 the shorter string for space optimization + if (len_trim(s1) > len_trim(s2)) then + str1 = trim(s2) + str2 = trim(s1) + else + str1 = trim(s1) + str2 = trim(s2) + end if + + m = len_trim(str1) + n = len_trim(str2) + + ! Use two arrays instead of full matrix for space optimization + allocate(prev_row(0:m), curr_row(0:m)) + + ! Initialize first row + do i = 0, m + prev_row(i) = i + end do + + ! Main computation loop + do j = 1, n + curr_row(0) = j + + do i = 1, m + ! Get characters at current position + c1 = str1(i:i) + c2 = str2(j:j) + + ! Calculate cost + if (c1 == c2) then + cost = 0 + else + cost = 1 + end if + + ! Calculate minimum of three operations + curr_row(i) = min(prev_row(i) + 1, & ! deletion + curr_row(i - 1) + 1, & ! insertion + prev_row(i - 1) + cost) ! substitution end do - ! Main computation loop - do j = 1, n - curr_row(0) = j - - do i = 1, m - ! Get characters at current position - c1 = str1(i:i) - c2 = str2(j:j) - - ! Calculate cost - if (c1 == c2) then - cost = 0 - else - cost = 1 - end if - - ! Calculate minimum of three operations - curr_row(i) = min(prev_row(i) + 1, & ! deletion - curr_row(i - 1) + 1, & ! insertion - prev_row(i - 1) + cost) ! substitution - end do - - ! Swap rows - prev_row = curr_row - end do + ! Swap rows + prev_row = curr_row + end do - distance = prev_row(m) + distance = prev_row(m) - ! Clean up - deallocate(prev_row, curr_row) - end function levenshtein_distance + ! Clean up + deallocate(prev_row, curr_row) + end function levenshtein_distance - end program main \ No newline at end of file +end program main \ No newline at end of file From 57583d180a9aa7180d6982e7c31310bc818f6d4a Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 21:55:03 +0100 Subject: [PATCH 12/14] Add in-process `loops` for Fortran --- loops/fortran/run.f90 | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 loops/fortran/run.f90 diff --git a/loops/fortran/run.f90 b/loops/fortran/run.f90 new file mode 100644 index 00000000..1eff9b2d --- /dev/null +++ b/loops/fortran/run.f90 @@ -0,0 +1,47 @@ +program main + use benchmark + implicit none + integer :: run_ms, warmup_ms, u + character(len=256) :: arg + type(benchmark_result_t) :: warmup_result, benchmark_result + character(len = :), allocatable :: result_str + + call get_command_argument(1, arg) + read(arg, *) run_ms ! Convert the command-line argument to integer + call get_command_argument(2, arg) + read(arg, *) warmup_ms ! Convert the command-line argument to integer + call get_command_argument(3, arg) + read(arg, *) u ! Convert the command-line argument to integer + + call run(loops_benchmark, warmup_ms, warmup_result) + call run(loops_benchmark, run_ms, benchmark_result) + + call format_results(benchmark_result, result_str) + write(*, '(A)') trim(adjustl(result_str)) + +contains + + integer(8) function loops_benchmark() + implicit none + integer :: i, j, r + integer(8) :: result + integer, dimension(10000) :: a + real :: random_value + + call random_seed() ! Initialize the random number generator + call random_number(random_value) ! Generate a random number (0 <= random_value < 1) + r = int(random_value * 10000) ! Scale and convert to an integer + + a = 0 ! Initialize the array with zeros + do i = 1, 10000 + do j = 0, 9999 + a(i) = a(i) + mod(j, u) + end do + a(i) = a(i) + r + end do + + result = a(r) + loops_benchmark = result + end function loops_benchmark + +end program main \ No newline at end of file From b0dd4af137b6bf712badfaadd7f1245d0aa3eb3a Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 22:59:40 +0100 Subject: [PATCH 13/14] Add in-process `fibonacci` for Fortran --- fibonacci/fortran/run.f90 | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 fibonacci/fortran/run.f90 diff --git a/fibonacci/fortran/run.f90 b/fibonacci/fortran/run.f90 new file mode 100644 index 00000000..79a40923 --- /dev/null +++ b/fibonacci/fortran/run.f90 @@ -0,0 +1,45 @@ +program main + use benchmark + implicit none + integer(8) :: n + integer(4) :: run_ms, warmup_ms + character(len=256) :: arg + type(benchmark_result_t) :: warmup_result, benchmark_result + character(len = :), allocatable :: result_str + + call get_command_argument(1, arg) + read(arg, *) run_ms ! Convert the command-line argument to integer + call get_command_argument(2, arg) + read(arg, *) warmup_ms ! Convert the command-line argument to integer + call get_command_argument(3, arg) + read(arg, *) n ! Convert the command-line argument to integer + + call run(fibonacci_benchmark, warmup_ms, warmup_result) + call run(fibonacci_benchmark, run_ms, benchmark_result) + + call format_results(benchmark_result, result_str) + write(*, '(A)') trim(adjustl(result_str)) + +contains + + integer(8) function fibonacci_benchmark() + implicit none + integer(8) :: result + + result = fibonacci(n) + fibonacci_benchmark = result + end function fibonacci_benchmark + + integer(8) recursive function fibonacci(n) result(f) + integer(8), intent(in) :: n + + if (n == 0) then + f = 0 + elseif (n == 1) then + f = 1 + else + f = fibonacci(n - 1) + fibonacci(n - 2) + end if + end function fibonacci + +end program main \ No newline at end of file From fdbd28c3df716519c1d7187372c3d3e43998ad22 Mon Sep 17 00:00:00 2001 From: PEZ Date: Sat, 25 Jan 2025 23:00:28 +0100 Subject: [PATCH 14/14] Fortran in-process: WIP: Fixing issue with hangs when running many times --- lib/fortran/benchmark.f90 | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/fortran/benchmark.f90 b/lib/fortran/benchmark.f90 index bef0ced4..19c1015d 100644 --- a/lib/fortran/benchmark.f90 +++ b/lib/fortran/benchmark.f90 @@ -24,8 +24,9 @@ end function f integer, intent(in) :: run_ms type(benchmark_result_t), intent(out) :: result integer(8) :: start_time, end_time, elapsed_time, total_elapsed_time + integer(8) :: count_rate integer :: count - real(8) :: elapsed_times(1000), mean, variance, std_dev, min_time, max_time + real(8) :: elapsed_times(1000000), mean, variance, std_dev, min_time, max_time logical :: print_status integer(8) :: last_status_t @@ -46,7 +47,7 @@ end function f min_time = 1.0e12 max_time = 0.0 print_status = (run_ms > 1) - call system_clock(last_status_t) + call system_clock(count_rate=count_rate) ! Get the count rate if (print_status) then write(0, '(A)', advance='no') "." @@ -54,16 +55,22 @@ end function f end if do while (total_elapsed_time < run_ms * 1.0e6) - call system_clock(start_time) + call system_clock(start_time, count_rate=count_rate) ! Use nanosecond precision result%result = func_ptr() - call system_clock(end_time) + call system_clock(end_time, count_rate=count_rate) ! Use nanosecond precision elapsed_time = end_time - start_time - elapsed_times(count + 1) = elapsed_time / 1.0e6 + if (elapsed_time == 0) cycle ! Skip zero elapsed time measurements + if (count < size(elapsed_times)) then + elapsed_times(count + 1) = elapsed_time / 1.0e6 + else + write(0,*) "Error: Exceeded maximum number of iterations" + exit + end if total_elapsed_time = total_elapsed_time + elapsed_time count = count + 1 if (elapsed_times(count) < min_time) min_time = elapsed_times(count) if (elapsed_times(count) > max_time) max_time = elapsed_times(count) - if (print_status .and. (end_time - last_status_t) > 1000000000) then + if (print_status .and. (end_time - last_status_t) > count_rate) then last_status_t = end_time write(0, '(A)', advance='no') "." flush(0)