Skip to content

Aula 01: A importância da linguagem

Nesta atividade, veremos como a escolha da linguagem de programação pode melhorar significativamente o desempenho de operações computacionalmente intensivas. Utilizaremos Python e C++ para implementar e comparar o desempenho na multiplicação de matrizes, uma operação comum em muitas aplicações de IA e visão computacional.

Parte 1: Implementação Básica em Python

Execute o código abaixo e observe o tempo de execução. Essa é uma implementação sequencial que processa a multiplicação de matrizes usando uma única thread em python.

import time  # Importa o módulo time, que fornece funções para medir o tempo de execução do código.

def multiply_matrices(A, B):
    # Define a função multiply_matrices, que realiza a multiplicação de duas matrizes A e B.
    # O resultado é armazenado na matriz result.
    result = [[0 for _ in range(len(B[0]))] for _ in range(len(A))]
    # Cria uma matriz de zeros com o mesmo número de linhas que A e o mesmo número de colunas que B.
    # Esta matriz armazenará os resultados da multiplicação.

    for i in range(len(A)):
        # Itera sobre as linhas da matriz A.
        for j in range(len(B[0])):
            # Itera sobre as colunas da matriz B.
            for k in range(len(B)):
                # Itera sobre as colunas de A e as linhas de B para calcular o produto escalar da linha i de A com a coluna j de B.
                result[i][j] += A[i][k] * B[k][j]
                # Realiza a multiplicação dos elementos correspondentes de A e B e soma o resultado ao elemento result[i][j].

    return result
    # Retorna a matriz result, que contém o produto das matrizes A e B.

# Gerar duas matrizes de tamanho grande para o teste
N = 200  # Define o tamanho N das matrizes quadradas (200x200).

A = [[i + j for j in range(N)] for i in range(N)]
# Gera a matriz A de tamanho N x N, onde cada elemento A[i][j] é a soma dos índices i e j.

B = [[i * j for j in range(N)] for i in range(N)]
# Gera a matriz B de tamanho N x N, onde cada elemento B[i][j] é o produto dos índices i e j.

start_time = time.time()
# Marca o tempo de início da multiplicação das matrizes usando a função time.time().

result = multiply_matrices(A, B)
# Chama a função multiply_matrices para multiplicar as matrizes A e B, armazenando o resultado em 'result'.

end_time = time.time()
# Marca o tempo de término da multiplicação usando a função time.time().

print(f"Tempo de execução para a multiplicação de matrizes: {end_time - start_time:.2f} segundos")
# Calcula e exibe o tempo de execução da multiplicação de matrizes, subtraindo start_time de end_time.
# O tempo é formatado para mostrar duas casas decimais.

Parte 2: Implementação em C++

Compile e execute o código em C++. Compare o tempo de execução com o resultado obtido na Parte 1. Observe como C++ lida com operações computacionalmente intensivas de forma mais eficiente.

Tip

Se precisar de ajuda para instalar um compilador em C++ ou compilar e executar códigos em c++, consulte o material disponível.

#include <iostream>   // Inclui a biblioteca padrão de entrada e saída do C++ (necessária para usar std::cout).
#include <vector>     // Inclui a biblioteca de vetores da STL (Standard Template Library) do C++, usada para criar matrizes dinâmicas.
#include <chrono>     // Inclui a biblioteca de medição de tempo do C++ (necessária para medir o tempo de execução).

// Função para multiplicar duas matrizes A e B, armazenando o resultado na matriz 'result'.
void multiply_matrices(const std::vector<std::vector<int>>& A, const std::vector<std::vector<int>>& B, std::vector<std::vector<int>>& result) {
   // Loop para iterar sobre as linhas da matriz A.
   for (size_t i = 0; i < A.size(); ++i) {
      // Loop para iterar sobre as colunas da matriz B.
      for (size_t j = 0; j < B[0].size(); ++j) {
            result[i][j] = 0; // Inicializa o elemento result[i][j] com 0 antes de somar os produtos.
            // Loop para calcular o produto escalar da linha i de A com a coluna j de B.
            for (size_t k = 0; k < B.size(); ++k) {
               result[i][j] += A[i][k] * B[k][j]; // Realiza a multiplicação e soma dos elementos correspondentes de A e B.
            }
      }
   }
}

// Função principal do programa.
int main() {
   size_t N = 200; // Define o tamanho N das matrizes quadradas (200x200).

   // Declara e inicializa a matriz A como uma matriz NxN preenchida com zeros.
   std::vector<std::vector<int>> A(N, std::vector<int>(N));

   // Declara e inicializa a matriz B como uma matriz NxN preenchida com zeros.
   std::vector<std::vector<int>> B(N, std::vector<int>(N));

   // Declara e inicializa a matriz result como uma matriz NxN preenchida com zeros, que armazenará o resultado da multiplicação.
   std::vector<std::vector<int>> result(N, std::vector<int>(N));

   // Loop para preencher as matrizes A e B com valores.
   for (size_t i = 0; i < N; ++i) {
      for (size_t j = 0; j < N; ++j) {
            A[i][j] = i + j; // Preenche a matriz A com a soma dos índices i e j.
            B[i][j] = i * j; // Preenche a matriz B com o produto dos índices i e j.
      }
   }

   // Marca o tempo de início da multiplicação das matrizes usando o relógio de alta resolução.
   auto start = std::chrono::high_resolution_clock::now();

   // Chama a função multiply_matrices para multiplicar as matrizes A e B, armazenando o resultado em 'result'.
   multiply_matrices(A, B, result);

   // Marca o tempo de término da multiplicação.
   auto end = std::chrono::high_resolution_clock::now();

   // Calcula a duração da multiplicação subtraindo o tempo de início do tempo de término, armazenando o resultado em 'duration'.
   std::chrono::duration<double> duration = end - start;

   // Exibe o tempo de execução da multiplicação de matrizes no console.
   std::cout << "Tempo de execução para a multiplicação de matrizes em C++: " << duration.count() << " segundos" << std::endl;

   return 0; // Retorna 0, indicando que o programa foi executado com sucesso.
}

Parte 3: Paralelismo em C++

Compile e execute o código modificado usando o comando '-fopenmp' para ter suporte ao paralelismo. Observe a diferença no tempo de execução em comparação com as versões anteriores.

g++ -fopenmp  meu_programa.cpp -o meu_executavel_paralelo

Warning

Lembre-se! Sempre que fizer alterações no seu código em c++, é necessário gerar um novo binário.

#include <iostream>   // Inclui a biblioteca padrão de entrada e saída, usada para funções como std::cout.
#include <vector>     // Inclui a biblioteca de vetores da STL (Standard Template Library) do C++.
#include <chrono>     // Inclui a biblioteca para medição de tempo, utilizada para calcular a duração de execução.
#include <omp.h>      // Inclui a biblioteca OpenMP, usada para paralelismo em C++.

void multiply_matrices(const std::vector<std::vector<int>>& A, const std::vector<std::vector<int>>& B, std::vector<std::vector<int>>& result) {
    // Define a função que realiza a multiplicação de duas matrizes A e B, armazenando o resultado na matriz 'result'.
    #pragma omp parallel for
    // Diretiva OpenMP que paraleliza o loop 'for' que segue. Cada iteração do loop externo será executada em paralelo.
    for (size_t i = 0; i < A.size(); ++i) {
        // Itera sobre as linhas da matriz A. 'size_t' é um tipo de dado usado para representar tamanhos e índices.
        for (size_t j = 0; j < B[0].size(); ++j) {
            // Itera sobre as colunas da matriz B.
            result[i][j] = 0;
            // Inicializa o elemento [i][j] da matriz result com 0 antes de somar os produtos.
            for (size_t k = 0; k < B.size(); ++k) {
                // Itera sobre as colunas de A e as linhas de B para calcular o produto escalar da linha i de A com a coluna j de B.
                result[i][j] += A[i][k] * B[k][j];
                // Realiza a multiplicação dos elementos correspondentes de A e B, somando o resultado ao elemento result[i][j].
            }
        }
    }
}

int main() {
    size_t N = 200;
    // Define o tamanho N das matrizes quadradas (200x200).
    std::vector<std::vector<int>> A(N, std::vector<int>(N));
    // Declara e inicializa a matriz A como uma matriz NxN preenchida com zeros.
    std::vector<std::vector<int>> B(N, std::vector<int>(N));
    // Declara e inicializa a matriz B como uma matriz NxN preenchida com zeros.
    std::vector<std::vector<int>> result(N, std::vector<int>(N));
    // Declara e inicializa a matriz result como uma matriz NxN preenchida com zeros, que armazenará o resultado da multiplicação.

    for (size_t i = 0; i < N; ++i) {
        // Itera sobre as linhas da matriz A e B.
        for (size_t j = 0; j < N; ++j) {
            // Itera sobre as colunas da matriz A e B.
            A[i][j] = i + j;
            // Preenche a matriz A com valores como a soma dos índices i e j.
            B[i][j] = i * j;
            // Preenche a matriz B com valores como o produto dos índices i e j.
        }
    }

    auto start = std::chrono::high_resolution_clock::now();
    // Marca o tempo de início da multiplicação das matrizes usando o relógio de alta resolução.
    multiply_matrices(A, B, result);
    // Chama a função multiply_matrices para multiplicar as matrizes A e B, armazenando o resultado em 'result'.
    auto end = std::chrono::high_resolution_clock::now();
    // Marca o tempo de término da multiplicação.

    std::chrono::duration<double> duration = end - start;
    // Calcula a duração da multiplicação subtraindo o tempo de início do tempo de término, armazenando o resultado em 'duration'.
    std::cout << "Tempo de execução para a multiplicação de matrizes em C++ com OpenMP: " << duration.count() << " segundos" << std::endl;
    // Exibe o tempo de execução da multiplicação de matrizes no console.

    return 0;
    // Retorna 0, indicando que o programa foi executado com sucesso.
}

Esta atividade demonstrou como a escolha da linguagem de programação pode impactar no desempenho de operações computacionalmente intensivas, como a multiplicação de matrizes. Você viu como Python e C++ lidam com essas operações e como o paralelismo pode ser uma boa opção, dependendo da complexidade do problema. Na próxima atividade, você levará essas implementações para o cluster Franky e essas diferenças ficarão ainda mais visíveis.

Entrega - Atividade 01

  • Aumente o tamanho das matrizes nos 3 exemplos para 300x300, 900x900 e 1300x1300

  • Elabore um gráfico que relaciona a complexidade do problema (tamanho da matriz) com o tempo de execução para cada implementação.

  • Faça uma análise comparativa sobre o impacto do paralelismo no desempenho de acordo com a complexidade do problema.

  • Comente sobre como você acha que este problema pode ser abordado em um ambiente de HPC.

Submeta seu relatório até as 23h59 de 07/02 pelo Classroom, disponível no Blackboard.