Fast and economic approaches for parsing large CSV files

Parsing large files is a recurring and challenging task. Right? It is too easy to write slow code that consumes a lot of memory.

As an example, let’s consider the following CSV file sample (the size of the real one is ~500MB)

userId,movieId,rating,timestamp
1,2,3.5,1112486027
1,29,3.5,1112484676
1,32,3.5,1112484819
1,47,3.5,1112484727
1,50,3.5,1112484580
1,112,3.5,1094785740
1,151,4.0,1094785734
1,223,4.0,1112485573
1,253,4.0,1112484940

Let’s assume that you need to compute the average rating for the ‘Braveheart’ movie (movieId=110). How would you implement it? Probably you would start with something like this:

var lines = File.ReadAllLines(filePath);
var sum = 0d;
var count = 0;

foreach (var line in lines)
{
    var parts = line.Split(',');

    if (parts[1] == "110")
    {
        sum += double.Parse(parts[2], CultureInfo.InvariantCulture);
        count++;
    }
}

Console.WriteLine($"Average rate for Braveheart is {sum/count} ({count} votes).");

The previous code is easy to read (that is good), but it is slow (took more than 6 seconds to run on my machine) and it consumes a lot of RAM (more than 2GB allocated processing a 500MB file).

The problem is we are loading all the data to the memory, putting a lot of pressure on the garbage collector. There is no need for doing that.

var sum = 0d;
var count = 0;
string line;

using (var fs = File.OpenRead(filePath))
using (var reader = new StreamReader(fs))
while ((line = reader.ReadLine()) != null)
{
    var parts = line.Split(',');

    if (parts[1] == "110")
    {
        sum += double.Parse(parts[2], CultureInfo.InvariantCulture);
        count++;
    }
}

Console.WriteLine($"Average rate for Braveheart is {sum / count} ({count} votes).");

This time we are loading data as we need and discarding it. This code is ~30% faster than the previous one, demands less memory (no more than 13MB for processing a 500MB file) and puts less pressure on the Garbage Collector (no more big objects nor objects that survive to the gen#0 collections).

Let’s try something different.

var sum = 0d;
var count = 0;
string line;

// Braveheart id movie id as span;
var lookingFor = "110".AsSpan();

using (var fs = File.OpenRead(filePath))
using (var reader = new StreamReader(fs))
while ((line = reader.ReadLine()) != null)
{
    // ignoring the voter id
    var span = line.AsSpan(line.IndexOf(',') + 1);

    // movieId
    var firstCommaPos = span.IndexOf(',');
    var movieId = span.Slice(0, firstCommaPos);
    if (!movieId.SequenceEqual(lookingFor)) continue;

    // rating
    span = span.Slice(firstCommaPos + 1);
    firstCommaPos = span.IndexOf(',');
    var rating = double.Parse(span.Slice(0, firstCommaPos), provider: CultureInfo.InvariantCulture);

    sum += rating;
    count++;
}

The primary goal of the previous code was to allocate fewer objects, to reduce the pressure on the garbage collector, getting better performance. Success! This code is 4x faster than the original one, consumes only 6MB and demands ~50% less garbage collector activations (Congrats, Microsoft!).

We are still allocating a string object for each line in the line. Let’s change it.

var sum = 0d;
var count = 0;

var lookingFor = Encoding.UTF8.GetBytes("110").AsSpan();
var rawBuffer =  new byte[1024*1024];
using (var fs = File.OpenRead(filePath))
{
    var bytesBuffered = 0;
    var bytesConsumed = 0;

    while (true)
    {
        var bytesRead = fs.Read(rawBuffer, bytesBuffered, rawBuffer.Length - bytesBuffered);

        if (bytesRead == 0) break;
        bytesBuffered += bytesRead;

        int linePosition;

        do
        {
            linePosition = Array.IndexOf(rawBuffer, (byte) '\n', bytesConsumed,
                bytesBuffered - bytesConsumed);

            if (linePosition >= 0)
            {
                var lineLength = linePosition - bytesConsumed;
                var line = new Span<byte>(rawBuffer, bytesConsumed, lineLength);
                bytesConsumed += lineLength + 1;


                // ignoring the voter id
                var span = line.Slice(line.IndexOf((byte)',') + 1);

                // movieId
                var firstCommaPos = span.IndexOf((byte)',');
                var movieId = span.Slice(0, firstCommaPos);
                if (!movieId.SequenceEqual(lookingFor)) continue;

                // rating
                span = span.Slice(firstCommaPos + 1);
                firstCommaPos = span.IndexOf((byte)',');
                var rating = double.Parse(Encoding.UTF8.GetString(span.Slice(0, firstCommaPos)), provider: CultureInfo.InvariantCulture);

                sum += rating;
                count++;
            }

        } while (linePosition >= 0 );

        Array.Copy(rawBuffer, bytesConsumed, rawBuffer, 0, (bytesBuffered - bytesConsumed));
        bytesBuffered -= bytesConsumed;
        bytesConsumed = 0;
    }
}

Console.WriteLine($"Average rate for Braveheart is {sum / count} ({count} votes).");

This time, we are loading the data in chunks of 1MB. The code seems a bit more complex (and it is). But, it runs almost 10x faster than the original one. Also, there are not enough allocations to activate the GC.

What do you think? How would you implement it? Share your thoughts in the comments.

Compartilhe este insight:

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mais insights para o seu negócio

Veja mais alguns estudos e reflexões que podem gerar alguns insights para o seu negócio:

In this post, I’m going to share with you one of the RavenDB 4 features that I like the most:...
Qual deveria ser o resultado da execução desse código? using System; using System.Threading.Tasks; using static System.Console; using static System.IO.File; class...
O ano era 2001 ou 2002. Não lembro ao certo. Eu era um jovem programador, pai recente, tentando “encontrar meu...
Hoje completo 39 anos! Estou em Uberlândia, em um hotel, descansando após um dia de muito trabalho (e um bocado...
Nunca trabalhei em um circo, tampouco criei elefantes! Portanto, advirto que os “fatos” que lerá aqui foram relatados por amigos,...
When designing systems that need to scale you always need to remember that [tweet]more computing power does not necessarily mean...

Curso Reputação e Marketing Pessoal

Masterclasses

01

Introdução do curso

02

Por que sua “reputação” é importante?

03

Como você se apresenta?

04

Como você apresenta suas ideias?

05

Como usar Storytelling?

06

Você tem uma dor? Eu tenho o alívio!

07

Escrita efetiva para não escritores

08

Como aumentar (e manter) sua audiência?

09

Gatilhos! Gatilhos!

10

Triple Threat: Domine Produto, Embalagem e Distribuição

11

Estratégias Vencedoras: Desbloqueie o Poder da Teoria dos Jogos

12

Análise SWOT de sua marca pessoal

13

Soterrado por informações? Aprenda a fazer gestão do conhecimento pessoal, do jeito certo

14

Vendo além do óbvio com a Pentad de Burkle

15

Construindo Reputação através de Métricas: A Arte de Alinhar Expectativas com Lag e Lead Measures

16

A Tríade da Liderança: Navegando entre Líder, Liderado e Contexto no Mundo do Marketing Pessoal

17

Análise PESTEL para Marketing Pessoal

18

Canvas de Proposta de Valor para Marca Pessoal

19

Método OKR para Objetivos Pessoais

20

Análise de Competências de Gallup

21

Feedback 360 Graus para Autoavaliação

22

Modelo de Cinco Forças de Porter

23

Estratégia Blue Ocean para Diferenciação Pessoal

24

Análise de Tendências para Previsão de Mercado

25

Design Thinking para Inovação Pessoal

26

Metodologia Agile para Desenvolvimento Pessoal

27

Análise de Redes Sociais para Ampliar Conexões

Lições complementares

28

Apresentando-se do Jeito Certo

29

O mercado remunera raridade? Como evidenciar a sua?

30

O que pode estar te impedindo de ter sucesso

Recomendações de Leituras

31

Aprendendo a qualificar sua reputação do jeito certo

32

Quem é você?

33

Qual a sua “IDEIA”?

34

StoryTelling

35

Você tem uma dor? Eu tenho o alívio!

36

Escrita efetiva para não escritores

37

Gatilhos!

38

Triple Threat: Domine Produto, Embalagem e Distribuição

39

Estratégias Vencedoras: Desbloqueie o Poder da Teoria do Jogos

40

Análise SWOT de sua marca pessoal

Inscrição realizada com sucesso!

No dia da masterclass você receberá um e-mail com um link para acompanhar a aula ao vivo. Até lá!

A sua subscrição foi enviada com sucesso!

Aguarde, em breve entraremos em contato com você para lhe fornecer mais informações sobre como participar da mentoria.

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano anual do Clube de Estudos:

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano mensal do Clube de Estudos:

× Precisa de ajuda?