C# - Usando tuplas e programação paralela para obter desempenho

 Hoje veremos na prática como usar tuplas para ajudar a obter desempenho na linguagem C#.

Tuplas

Uma tupla é uma estrutura de dados que tem um número específico e sequencial de elementos.

Um exemplo de uma tupla é uma estrutura de dados com três elementos (conhecido como uma tupla de 3 ou o triplo) que é usada para armazenar um identificador, como o nome da pessoa no primeiro elemento, um ano no segundo elemento e a renda da pessoa no terceiro elemento.

Assim uma  tupla permite agregar valores, e o caso mais comum é permitir múltiplos valores de retorno de uma função sem precisar de parâmetros “out”.

Uma tupla pode ser formada por 1, 2, 3 ou mais itens, com os tipos de suas diferentes propriedades e seus respectivos valores sendo declarados entre parênteses.

Para saber mais sobre tuplas consulte os citados artigos nas referências.

Usando Tuplas

Neste artigo vamos usar os recursos das tuplas para realizar uma tarefa que exige desempenho.

A título de exercício prático vamos acessar e abrir um arquivo CSV chamado Vendas_2021.csv que possui aproximadamente 5 milhões de registros, e, vamos procurar fazer isso em um menor tempo possível usando a linguagem C#.

Vamos criar um projeto Console no .NET Core 5.0, criar uma tupla para receber os dados e a seguir vamos acessar, carregar e exibir alguns dados. Nosso objeto será abrir e carregar os dados na memória de forma rápida e depois exibir apenas 10 itens.

A estrutura do arquivo CSV usada é a seguinte:

Region,Country,Item Type,Sales Channel,Order Priority,Order Date,Order ID,Ship Date,Units Sold,Unit Price,Unit Cost,Total Revenue,Total Cost,Total Profit
Australia and Oceania,Palau,Office  Supplies,Online,H,3/6/2016,517073523,3/26/2016,2401,651.21,524.96,1563555.21,1260428.96,303126.25
Europe,Poland,Beverages,Online,L,4/18/2010,380507028,5/26/2010,9340,47.45,31.79,443183.00,296918.60,146264.40
North America,Canada,Cereal,Online,M,1/8/2015,504055583,1/31/2015,103,205.70,117.11,21187.10,12062.33,9124.77
Europe,Belarus,Snacks,Online,C,1/19/2014,954955518,2/27/2014,1414,152.58,97.44,215748.12,137780.16,77967.96
Middle East and North Africa,Oman,Cereal,Offline,H,4/26/2019,970755660,6/2/2019,7027,205.70,117.11,1445453.90,822931.97,622521.93
Sub-Saharan Africa,Burkina Faso,Office Supplies,Online,C,3/3/2012,309317338,4/5/2012,2729,651.21,524.96,1777152.09,1432615.84,344536.25
Europe,Montenegro,Personal Care,Online,H,11/24/2012,598814380,12/25/2012,1337,81.73,56.67,109273.01,75767.79,33505.22
....

O cabeçalho do arquivo possui 14 itens que podemos identificar pelo índice de 0 a 13.

Para isso vamos criar uma lista de tuplas com três itens do tipo string e vamos selecionar os itens : Region, Country e Total Profit ou seja os itens value[0], value[1] e value[13]

Assim usando a notação antiga das Tuplas vamos criar uma lista de tuplas chamada ListaVendas:

 List<Tuple<string, string, string>> ListaVendas = new List<Tuple<string, string, string>>();

A seguir vamos verificar se o arquivo .csv  na pasta c:\dados\csv existe, e, a seguir vamos abrir e ler o arquivo usando um StreamReader, lendo linha a linha do arquivo usando a função split() e o separador ',' para separar os itens da lista e a seguir preenchemos a lista de tuplas com os valores obtidos de cada linha lida:

if (File.Exists(@"C:\Dados\csv\Vendas_2021.csv"))
{
     using (var reader = new StreamReader(@"C:\Dados\csv\Vendas_2021.csv"))
     {
             while (!reader.EndOfStream)
             {
                  var linha = reader.ReadLine();
                  var valor = linha.Split(',');
                  ListaVendas.Add(new Tuple<string, string, string>(valor[0], valor[1], valor[13]));
             }
       }
}

Após isso basta processar a lista realizando uma consulta LINQ onde vamos obter apenas os 10 primeiros itens da lista ordenados pelo valor do item Total Profit e exibir no console:

 //PROCESSAMENTO
var listaDezResultados = from vendas in ListaVendas.Skip(0).Take(10)
                                   orderby vendas.Item3
                                   select vendas;

//EXIBIÇÃO DOS DADOS
foreach (var item in listaDezResultados)
{
       Console.WriteLine($"{item.Item1} - {item.Item2} - {item.Item3}");
}

stopwatch.Stop();
Console.WriteLine($"Tempo gasto {stopwatch.ElapsedMilliseconds / 1000}");

O código completo é visto abaixo:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace C_Desempenho1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("Iniciar Processamento");
            Console.ReadKey();
            CarregarDados();
            Console.ReadLine();
        }

        static private void CarregarDados()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            //Carrega dados
            List<Tuple<string, string, string>> ListaVendas = new List<Tuple<string, string, string>>();

            if (File.Exists(@"C:\Dados\csv\Vendas_2021.csv"))
            {
                using (var reader = new StreamReader(@"C:\Dados\csv\Vendas_2021.csv"))
                {
                    while (!reader.EndOfStream)
                    {
                        var linha = reader.ReadLine();
                        var valor = linha.Split(',');
                        ListaVendas.Add(new Tuple<string, string, string>(valor[0], valor[1], valor[13]));
                    }
                }

                //PROCESSAMENTO   
                var listaDezResultados = from vendas in ListaVendas.Skip(0).Take(10)
                                                         orderby vendas.Item3
                                                        select vendas;


                //EXIBIÇÃO DOS DADOS
                foreach (var item in listaDezResultados)
                {
                    Console.WriteLine($"{item.Item1} - {item.Item2} - {item.Item3}");
                }

                stopwatch.Stop();
                Console.WriteLine($"Tempo gasto {stopwatch.ElapsedMilliseconds / 1000}");
            }
        }
    }
}

Executando o projeto iremos obter o resultado a seguir:

Como vemos gastamos 6 segundos para carregar 5 milhões de registro na memória o que não é um tempo tão ruim.

"Nisto consiste o amor: não em que nós tenhamos amado a Deus, mas em que ele nos amou e enviou o seu Filho como propiciação pelos nossos pecados."
1 João 4:10


Referências:


José Carlos Macoratti