Desarrollo de software verde

Green Computing aplicado a la ingeniería del software

Curso de verano, CEMED

Ingeniería de la optimización energética

Los perfiles energéticos se hacen para programas completos

Hay que extraer las funciones que más CPU usan para medir el consumo

Una metodología para medir consumo

Ejecución con parámetros diferentes

Un número fijo de llamadas

Repetición para significación estadística

Es imposible medir solamente nuestro programa

Se establece una línea base

La estadística es importante

Generación de cromosomas en C++

#include <tr2/dynamic_bitset>
#include <random>
std::vector<std::tr2::dynamic_bitset<>>&
  generate_chromosomes(unsigned int number_of_elements, unsigned int length){
  std::minstd_rand engine;
  static std::vector<std::tr2::dynamic_bitset<>> population;
  population.resize(number_of_elements);
  for (unsigned i = 0; i < number_of_elements; ++i)
  {
    std::tr2::dynamic_bitset<> bits(length);
    for (unsigned i = 0; i < length; ++i)
      bits[i] = engine() & 1;
    population[i] = bits;
  }
  return population;
}

Compilamos con

g++-14
			      -march=native
			      -std=c++2a
			      -Wall
			      chromosomes.cc generate_chromosomes.cc -o chromosomes

Hay que medir las prestaciones

#!/usr/bin/env sh
FILENAME=$1
for i in 512 1024 2048
do
    echo "Size ${i}"
    OUTPUT="${FILENAME}-${i}.json"
    echo "Saving to ${OUTPUT}"
    pumas run -i 100 --json > $OUTPUT &
    for j in $(seq 15)|
    do
        C++/chromosomes $i
    done
    kill $!
done
			  

Midiendo en un Mac

cat test-512.json | jq ". | .metrics.consumption"
{
  "ane_w": 0,
  "cpu_w": 4.5013532638549805,
  "gpu_w": 0.02891661413013935,
  "package_w": 4.530270099639893
}

Más fácil con pinpoint

sub process_pinpoint_output {
  my $output = shift;
  if ($output !~ /0.00\s+J/) {
      my ( $gpu, $pkg ) = $output =~ /(\d+\.\d+)\s+J/g;
      my ( $seconds ) = $output =~ /(\d+\.\d+) seconds/;
      return $gpu, $pkg,$seconds;
    } else {
      return 0,0,0;
    }
}	

Hay que instrumentar la medición de consumo

Y trabajar con estadísticas

Esta operación común permite establecer la línea base

Pero nos interesa una función específica

Midiendo operaciones específicas

import { hashify } from "https://deno.land/x/saco@v0.0.2/index.js";
import { countOnes, generateChromosomes } from "../lib/utils.js";
const size = Deno.args[0];
const NUMBER_OF_CHROMOSOMES = 40000;
console.log("Size ", size);
const population = generateChromosomes(size, NUMBER_OF_CHROMOSOMES);
const fitnessArray = [];
population.forEach((c) => {
  fitnessArray.push(countOnes(c));
});
		      

Cualquier cambio en la implementación de un algoritmo cambiará su perfil energético

Ese efecto no tiene por qué ser intuitivo

Cambiar CPU por memoria, por ejemplo

Conviene acompañar la intuición con la medición

Medida más simple: cambios en la compilación/intérprete

Compilación sin info depuración/optimización

Menos uso de memoria, menos CPU

Cambio en el uso de instrucciones específicas de procesador

g++-14
			      -march=native
			      -std=c++2a
			      -Wall
			      chromosomes.cc generate_chromosomes.cc -o chromosomes

Los compiladores permiten decidir qué instrucciones se incluyen en el ejecutable

Y opciones del compilador

g++-12
			      -march=native
			      -Ofast
			      -std=c++17
			      -Wall
			      chromosomes.cc generate_chromosomes.cc -o chromosomes

Argumentos de JVM permiten cambiar el comportamiento energético

-server para optimizar, -X para diferentes parámetros

Python permite engancharlo con perf

python -X perf programa.py

node.js no tiene muchas opciones

--jitless impide asignación de memoria...

Mucho margen en compiladores, menos en intérpretes y JVM

Probar con diferentes máquinas, mismo SO

Portátiles, más nuevos: mejor gestión

Cambiar Intel/Linux → Mac/Apple Silicon

Las diferencias, sobre todo en M2/3, son dramáticas

Diferentes lenguajes → diferente perfil de consumo

Interpretados, tipos "duck" → alto consumo

JVM, ¿otros runtimes? → consumo medio

Lenguajes compilados → bajo consumo

Lenguajes funcionales compilados → bajísimo consumo

Experimentar con diferentes runtimes/compiladores

Oracle JDK/OpenJDK/IntelliJ JDK

CPython/PyPy

node.js/deno/bun

Trabajar con las últimas versiones estables

En general, mejoran rendimiento y consumo

Siempre diseñar experimentos con las funciones en cuallo de botella

No asumas nunca nada

Diseña experimentos y perfila