Субъективное сравнение скорости работы Python3 и Golang в задаче записи чисел в 2 файла
Написал идентичный код на обоих языках :
import os
file1 = open("z1.txt", "w")
file2 = open("z2.txt", "w")
for x in range(-1000, 1000):
for y in range(-1000, 1000):
z1 = (x + y) * (x + y)
z2 = x * y
args = str(x) + "\t" + str(y) + "\t"
toFile1 = args + str(z1) + "\n"
toFile2 = args + str(z2) + "\n"
file1.write(toFile1)
file2.write(toFile2)
file1.close()
file2.close()
package main
import (
"os"
"strconv"
)
func main() {
file1, _ := os.Create("z1.txt")
file2, _ := os.Create("z2.txt")
for x := -1000; x < 1000; x++ {
for y := -1000; y < 1000; y++ {
z1 := (x + y) * (x + y)
z2 := x * y
args := strconv.Itoa(x) + "\t" + strconv.Itoa(y) + "\t"
toFile1 := []byte(args + strconv.Itoa(z1) + "\n")
toFile2 := []byte(args + strconv.Itoa(z2) + "\n")
file1.Write(toFile1)
file2.Write(toFile2)
}
}
file1.Close()
file2.Close()
}
Неожиданный сюрприз - на golang работает 12 секунд в худшем случае,а на python за 7. В среднем случае гоу тоже сильно отстаёт, было проведено коло 30 запусков. Более того, golang сам по себе работает в 2 потока с этой программой, что казалось бы должно ускорить запись в оба файла.
У меня было подозрение, что я в гоу использовал слишком низкоуровневые функции как-то неверно и решил реализвать тот же код на си:
#include "stdio.h"
int main(){
FILE* f1 = fopen("z1.txt","w");
FILE* f2 = fopen("z2.txt","w");
for (int x =-1000; x <1000;++x ){
for(int y =-1000; y <1000;++y ){
int z1 = (x+y)*(x+y);
int z2 = x*y;
fprintf(f1,"%d\t%d\t%d\n",x,y,z1);
fprintf(f2,"%d\t%d\t%d\n",x,y,z2);
}
}
fclose(f1);
fclose(f2);
return 0;
}
Тут ничего удивительного - си выполняет всё мигом. Ладно, попробовал в гоу сишную реализацию, стало ещё хуже - 22секунды в среднем.
package main
import (
"fmt"
"os"
)
func main() {
file1, _ := os.Create("z1.txt")
file2, _ := os.Create("z2.txt")
for x := -1000; x < 1000; x++ {
for y := -1000; y < 1000; y++ {
z1 := (x + y) * (x + y)
z2 := x * y
fmt.Fprintf(file1, "%d\t%d\t%d\n", x, y, z1)
fmt.Fprintf(file2, "%d\t%d\t%d\n", x, y, z2)
}
}
file1.Close()
file2.Close()
}
Далее Я попробовал оптимизировать код на го и сделал запись в файл 1 раз, вместо записи в цикле, и хранил буфер в одной строке, аналогично сделал на питоне и ничего не изменилось, я не могу понять в чём дело, почему golang в этой задаче отстаёт.
Версия с рукописным буфером го:
package main
import (
"os"
"strconv"
)
func main() {
file1, _ := os.Create("z1.txt")
file2, _ := os.Create("z2.txt")
var toFile1 string
var toFile2 string
for x := -1000; x < 1000; x++ {
for y := -1000; y < 1000; y++ {
z1 := (x + y) * (x + y)
z2 := x * y
args := strconv.Itoa(x) + "\t" + strconv.Itoa(y) + "\t"
toFile1 += (args + strconv.Itoa(z1) + "\n")
toFile2 += (args + strconv.Itoa(z2) + "\n")
}
}
file1.Write([]byte(toFile1))
file2.Write([]byte(toFile2))
file1.Close()
file2.Close()
}
По возможносте дайте пожалуйста подробный ответ, а не "в гоу там доп действие - конвертация в байты". Заранее спасибо)
python3 Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux Type "help", "copyright", "credits" or "license" for more information.
go version go1.21.6 linux/amd64
Ответы (1 шт):
Файлы в Го лишены буферов. Каждая операция записи в файл приводит к записи на диск. Программа получает управление после того как от операционной системы получена гарантия что данные записаны на диск. Тест:
$ time go run temp.go real 0m12.011s user 0m4.660s sys 0m7.384s
Огромное время в строке sys - время ожидания записи данных на диск.
Добавим буфера:
package main
import (
"bufio"
"os"
"strconv"
)
func main() {
file1, _ := os.Create("z1.txt")
file2, _ := os.Create("z2.txt")
buf1 := bufio.NewWriter(file1)
buf2 := bufio.NewWriter(file2)
for x := -1000; x < 1000; x++ {
for y := -1000; y < 1000; y++ {
z1 := (x + y) * (x + y)
z2 := x * y
args := strconv.Itoa(x) + "\t" + strconv.Itoa(y) + "\t"
toFile1 := []byte(args + strconv.Itoa(z1) + "\n")
toFile2 := []byte(args + strconv.Itoa(z2) + "\n")
buf1.Write(toFile1)
buf2.Write(toFile2)
}
}
buf1.Flush()
buf2.Flush()
file1.Close()
file2.Close()
}
Стало гораздо лучше:
$ time go run temp.go real 0m1.554s user 0m1.428s sys 0m0.112s
В Питоне и в Си буфер встроен прямо в объект-файл, вы сразу получаете хорошую производительность, но нет гарантий записи данных прямо в момент записи. Гарантия появляется только после сброса буфера.
Для ориентировки ваш код на Си у меня отрабатывает почти наравне с буферированным Го:
$ time ./a.out real 0m1.336s user 0m1.168s sys 0m0.080s
А вот что будет если в Си добавить сброс буферов в цикл (fflush(f1); fflush(f2);). Снова семь секунд ожидания записи:
$ time ./a.out real 0m11.220s user 0m4.220s sys 0m6.912s