Сколько же строк в файле…


Задумавшись однажды над написанием кое-какого приложения у меня возникла нужда считать количество строк в файлах. Считать быстро. А файлы бывают большими (500–1500 Мб). И, поскольку в тот момент я находился на больничном, возник спортивный интерес проверить, какой язык произведёт сие действо за минимальное время.

TL;DR; Go — победил.

Тестовые программы


Итак, были выбраны доступные мне языки:

  • linesCount.c
    #include <stdio.h>
    #include <time.h>
    
    int main(int argc, const char * argv[]) {    
        const char *path = "big.csv";
        
        double startTime = clock();
        
        int linesCount = 0;
        FILE *fp = fopen(path, "r");
        int bufsize = 32*1024;
        char buff[bufsize];
        while (fgets(buff, bufsize, fp) != NULL) {
            linesCount++;
        }
        fclose(fp);
        
        double endTime = clock();
        double totalTime = (endTime - startTime) / CLOCKS_PER_SEC;
        printf("Time: %f s\n", totalTime);
        printf("Lines: %d \n", linesCount);
        
        return 0;
    }
    

  • C# (Mono/macOS, .Net/Windows)
    using System;
    using System.IO;
    
    namespace lineStat
    {
    	class MainClass
    	{
    		public static void Main(string[] args)
    		{
    			var filename = "big.csv";
    
    			var startTime = DateTime.Now;
    			var linesCount = countLinesInFile(filename);
    			var spentTime = DateTime.Now - startTime;
    			Console.WriteLine("Lines count: {0} in {1}", linesCount, spentTime);
    		}
    
    		static int countLinesInFile(string filename)
    		{
    			int linesCount = 0;
    			using (var reader = new StreamReader(filename))
    			{
    				while (reader.ReadLine() != null) {
    					linesCount++;
    				}
    			}
    			return linesCount;
    		}
    	}
    }

  • Go
    package main
    
    import (
    	"bytes"
    	"fmt"
    	"io"
    	"os"
    	"time"
    )
    
    func main() {
    
    	start_time := time.Now()
    	file, _ := os.Open("big.csv")
    	count, _ := lineCounter(file)
    	elapsed := time.Since(start_time)
    	fmt.Printf("Lines count: %d in %s\n", count, elapsed)
    }
    
    func lineCounter(r io.Reader) (int, error) {
    	buf := make([]byte, 32*1024)
    	count := 0
    	lineSep := []byte{'\n'}
    
    	for {
    		c, err := r.Read(buf)
    		count += bytes.Count(buf[:c], lineSep)
    
    		switch {
    		case err == io.EOF:
    			return count, nil
    
    		case err != nil:
    			return count, err
    		}
    	}
    }

  • Java
    package me.meamka;
    
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    import java.text.DecimalFormat;
    import java.text.NumberFormat;
    
    public class LinesCount {
    
        public static void main(String[] args) throws IOException {
    	// write your code here
            String filename = "big.csv";
            System.out.println("Count lines in " + filename);
            long startTime = System.nanoTime();
            int linesCount = countLinesInFile(filename);
            long endTime = System.nanoTime();
            NumberFormat formatter = new DecimalFormat("#0.00000");
            System.out.println("Lines count: " + linesCount +" in " + formatter.format((endTime - startTime) / 1000000000d));
        }
    
        private static int countLinesInFile(String filename) throws IOException {
            int linesCount = 0;
    
            try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
                while (br.readLine() != null) {
                    linesCount++;
                }
            }
            return linesCount;
        }
    }
  • PHP
    <?php
    function countLinesInFile() {
    	$f = fopen('big.csv', 'r');
    	$lineCount = 0;
    	while (!feof($f))
    	{
    		if (fgets($f))
            $lineCount++;
    	}
    	fclose($f);
    	return $lineCount;
    }
    function main() {
    	$start = microtime(true);
    	$linesCount = countLinesInFile();
    	$duration = number_format(microtime(true)-$start, 4);
    	$memory = number_format(memory_get_peak_usage(true),0,'.',' ');
    	echo "Total lines: $linesCount; Duration: $duration seconds; Memory used: $memory bytes\n";
    }
    main();
    ?>

  • Python
    # coding: utf-8
    import time
    
    def linesCount(filename):
        count = 0
        with open(filename, 'r') as reader:
            while reader.readline():
                count += 1
    
        return count
    
    
    if __name__ == '__main__':
        filename = 'big.csv'
    
        start_time = time.time()
        count = linesCount(filename)
        end_time = time.time() - start_time
    
        print("Lines count: %s in %s" % (count, end_time))

  • Swift
    import Foundation
    import Darwin
    
    
    var path = "big.csv"
    
    let methodStart = NSDate()
    
    var linesCount = 0
    let bufsize = 32*1024
    var buf = UnsafeMutablePointer<Int8>.allocate(capacity: bufsize)
    
    let filePointer = fopen(path, "r")
    
    while fgets(buf, Int32(bufsize-1), filePointer) != nil {
        //    print(String.fromCString(CString(buf)))
        linesCount += 1
    }
    fclose(filePointer)
    
    let methodFinish = Date()
    let executionTime = methodFinish.timeIntervalSince(methodStart as Date)
    print("Execution time: \(executionTime)")
    
    print("Lines Count: \(linesCount)")


Тестовая среда


Все действия и тесты (кроме .Net) производились на MacBook Pro 13 Late 2012 с i5 2.5 GHz, SSD 256 Gb и 8 Gb ОЗУ, macOS 10.12.1.

Файл для тестов был найден в виде .csv размером 491,4 Мб и 489286 строк.

Для тестов были написаны простейшие консольные приложения состоящие из 2х функций: собственно, основной функции и функции, которая считает кол-во строк.

Результаты


C ? 0.25 сек
C# ? 2.8 сек
Go ? 0.16 сек
Java ? 2.7 сек
PHP ? 0.25 сек
Python ? 2.6 сек
Objective-C/Swift ? 0.25 сек

Оговорюсь, что я не делал большого количества итераций, остановившись на 20. В результаты взял среднее значение.

Из интересного


  • Первые тесты на C# (Mono) показали ужасающие 7.5 секунд, в то время как версия под Windows показала 3 секунды. Полагаю, ОС была чем-то занята, потому как на следующий день тесты выдали терпимые 2.5-3 сек.
  • Никак не ожидал, что будет что-то быстрее C. Go удивил
  • Не ожидал подобную скорость от PHP. Но C-шные вызовы дают о себе знать.
  • Objective-C/Swift благодаря возможности встроить C-код показали аналогичный самому C результат. Я смухлевал

P. S.


Исходные файлы на всякий случай прилеплены в Gist: исходные коды.

Если кто-то желает протестировать и поделиться результатами — милости прошу, мне крайне интересно.

И, повторюсь, интерес у меня крайне спортивный, стоит воздержаться от комментариев «Java — ***, а C — рулит!».

Удачи друзьям груммелей!
-->


К сожалению, не доступен сервер mySQL