Java ¿se quedó atrás?

Hace poco en la organización donde trabajo se reitero el uso obligatorio del framework desarrollado en casa y que su lenguaje base es Java y sus tecnologías adyacentes.

Pensando en ese escenario se pensó en hacer un caso de uso (hacer frecuencias de ciertas variables de un archivo de texto) para que con estas cifras la generación aleatoria de datos de los llamados sintéticos tengan cierta tendencia de acuerdo a los datos reales, usando Java.

Quizás a veces nos quedamos "congelados" con una forma de trabajar y debido a la gran velocidad y potencia que tiene Apache Spark para el procesamiento de archivos de gran tamaño, nos quedamos que es lo único que resuelve este caso.

En el equipo se dio un pase adelante con la conversión de los archivos fuentes a parquet (ya se escribirá al respecto de este formato) y con ellos se disminuyó aun más el tiempo de procesamiento.

Pero lo que no esperaba es que con un "clásico" procesamiento secuencial pudiera "ganarle" a Apache Spark, según lo investigado con el apoyo de la Inteligencia artificial usar la lectura secuencial en Java pudiera ser más rápido que el uso de Spark con R.

Primero se usará RServer para hacer este proceso, también cada conteo de variables se guarda en archivos de tipo  texto delimitado.


El tiempo de ejecución se puede observar fue de 29.1 segundos, con apoyo de la Inteligencia Artificial (el buen Deep) se probó con otros 2 métodos en el lenguaje de programación R, pero no se consiguió bajar el tiempo de este código.
Un tema que quizás se debe explorar más es la configuración de Spark en el uso de los recursos del o de los equipos (se pueden implementar clusters de computadoras). En este ejercicio se usó esta configuración.

#Poner la cantidad de memoria RAM según nuestro equipo
configuracion$`sparklyr.shell.driver-memory` <- "256G"
configuracion$spark.memory.fraction <- "0.7"

Por lo demás tenemos instrucciones usando la librería dplyr.

#Agrupación salario (columna sal) por rangos empezando desde 1 SM = 122
# con incrementos de 3 hasta 14 SM
rangos <- c(0,122*3,122*6,122*9,122*12,122*15,Inf)
nombres <- c("0-3","4-6","7-9","10-12","13-15","15+")
dsPtosTrab <- dsPtosTrab %>%
  mutate(rango_sal = cut(as.numeric(sal), breaks = rangos, labels = nombres, right = FALSE))
tabagrup4 <- dsPtosTrab %>%
  group_by(rango_sal) %>% 
  summarise(total = n()) %>% arrange(rango_sal) %>% 
  collect()

write.csv(tabagrup4,"tabagrup4_122.csv")

La idea original era que Java podía trabajar con Spark (de hecho para usar sparklyr) es necesario tener la máquina virtual de Java, y ese fue el detonante para "descubrir" que usando una instancia de Visual Studio Code en un equipo desktop/laptop conecte con una poderosa Estación de Trabajo, ya que se necesitaba "escribir" el programa usando algún editor o bien un entorno de desarrollo integrado (IDE). El modo gráfico de la estación de trabajo esta desactivado por lo que la alternativa de usar VS Code con la extensión Remote-SSH.

Ver:


Otra cosa interesante fue el uso de OpenJDK en lugar del producto comercial de Java (JDK) y fue muy transparente, solo para apoyar en la codificación se instalaron extensiones relacionadas con el lenguaje de programación Java, todo esto de forma transparente y con una versión del Toolkit de desarrollo de Java muy reciente.

user@nombre_edet:~/Java$ java --version
openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Ubuntu-125.10)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Ubuntu-125.10, mixed mode, sharing)
user@nombre_edet:~/Java$ 

Con esta experiencia se llega a la reflexión que a veces las formas de trabajar matan y se hace rutinario el usar un método, en este caso el compañero Deep sugirió hacer una prueba de la "vieja escuela" leer secuencialmente el archivo, sumar cada categoría de forma individual y al final lanzar los resultados a archivos de texto delimitado (csv cuando el delimitador es coma). Aquí vemos el resultado de la corrida del programa en Java.


Con esta forma de hacer los cálculos nos damos cuenta que el tiempo de procesamiento baja a 13.8 segundos.
Para este ejercicio se usa uno de los arreglos con esteroides llamado Map(HashMap) y existe una colección por cada agrupamiento.

 // 1. Clave mod + registro
 Map<String, Integer> contadorModReg = new HashMap<>();
 // 2. nss (primeros 3 caracteres)
 Map<String, Integer> contadorNss = new HashMap<>();     
 // 3. Contador rango salarial
 Map<String, Integer> contadorRangoSal = new HashMap<>();
 // 4. Contador sexo
 Map<String, Integer> contadorSexo = new HashMap<>();
 // 5. Contador mod
 Map<String, Integer> contadorMod = new HashMap<>();
 // 6. Contador ciz
 Map<String, Integer> contadorCiz = new HashMap<>();
 // 7. Contador registro
 Map<String, Integer> contadorRegistro = new HashMap<>();
 // 8. Contador año de nacimiento
 Map<String, Integer> contadorAnioNac = new HashMap<>();   


Aquí la lectura de los archivos, la separación de cada línea de texto por el delimitador ('|') por medio del método split() y recuperar cada columna de acuerdo a su posición.

while((linea = br.readLine())!=null){
    String[] partes = linea.split("\\|");

    if(partes.length<3) continue;

    dimension = partes.length;

    String mod = partes[6];
    String registro = partes[5];
    String nss = partes[0];

    ...

Para el cálculo de cada agrupamiento se utiliza el método merge() de la colección. 

String clave = mod + "|" + registro;

// Conteo clave (mod + registro)
contadorModReg.merge(clave, 1, Integer::sum);

//Conteo tres primeros caracteres del nss
String agruparNss = nss.length() >= 3 ? nss.substring(0, 3) : nss;
contadorNss.merge(agruparNss, 1, Integer::sum);  

Al terminar la lectura secuencial cada colección, los resultados de la misma se manda a archivos de texto y para ello se programan funciones para realizar esto.

// Usamos las funciones
// Frecuencias para clave (mod + registro)
escribirCSVModReg(contadorModReg, "Tabgrup_mod_registro.csv");
//Frecuencias para nss
escribirCSVOrdenadoPorValorDesc(contadorNss,"Tabgrup_nss.csv");
//Frecuencias rangos de salario
escribirCSVRangos(contadorRangoSal, "Tabgrup_rango_salarial.csv");
//Frecuencias para sexo
escribirCSVSexo(contadorSexo, "Tabgrup_sexo.csv");

Tenemos también una de las funciones que mandan los resultados a los archivos con algún tipo de ordenamiento.

static void escribirCSVOrdenadoPorValorDesc(Map<String, Integer> mapa, String archivo) throws Exception {
    List<Map.Entry<String, Integer>> lista = new ArrayList<>(mapa.entrySet());
    lista.sort((a,b) -> b.getValue().compareTo(a.getValue()));
    try(PrintWriter pw = new PrintWriter(new FileWriter(archivo))){
        pw.println("clave,total");
        for(Map.Entry<String, Integer> e: lista){
            pw.println(e.getKey() + "," + e.getValue());                    
        }
    }
    System.out.println("<Ok>" + archivo);
}

Se puede concluir que la diferencia estriba en que con Spark se carga todo el contenido a memoria RAM como a memoria secundaria (el disco duro) y en Java solo se va leyendo uno a uno y cada caso se procesa de forma individual.
Aunque el archivo tiene un tamaño considerable (alrededor de 2Gb, 12 columnas y más de 22 millones de registros) el pronóstico de la IA, es que ante archivos más grandes se debería de aplicar otra estrategia.
Nuevamente seguiremos relatando aquí casos de "Big Compute" y otros más que tiene que ver con lenguajes de programación y sus tecnologías adyacentes.

Hasta el siguiente artículo.

Miguel Araujo.
 

Comentarios

Entradas populares de este blog

Visual Studio Code con Python usando base de datos de Oracle

Después de Xamarin - .NET MAUI para aplicaciones móviles (y multiplataforma)

Sonidos en .NET MAUI y la duda sobre Miriam