hchiorean - Initial version
* Initial version * Removed some System out messages and tweaked some config values * Added some fixes and some tweaks
This commit is contained in:
parent
e66a2c96a3
commit
1a9b1cb7da
19
calculate_average_hchiorean.sh
Normal file
19
calculate_average_hchiorean.sh
Normal file
@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright 2023 The original authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
JAVA_OPTS="-Xms1000M"
|
||||
time java $JAVA_OPTS --class-path target/average-1.0.0-SNAPSHOT.jar dev.morling.onebrc.CalculateAverage_hchiorean
|
205
src/main/java/dev/morling/onebrc/CalculateAverage_hchiorean.java
Normal file
205
src/main/java/dev/morling/onebrc/CalculateAverage_hchiorean.java
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2023 The original authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.morling.onebrc;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class CalculateAverage_hchiorean {
|
||||
|
||||
private static Map<String, double[]> parseLines(Integer key, CharBuffer chars, ConcurrentMap<Integer, String> leftoversMap) {
|
||||
Map<String, double[]> data = new HashMap<>();
|
||||
|
||||
int startIdx = 0;
|
||||
int endIdx = chars.length() - 1;
|
||||
while (chars.charAt(startIdx) != '\n') {
|
||||
++startIdx;
|
||||
}
|
||||
while (chars.charAt(endIdx) != '\n') {
|
||||
--endIdx;
|
||||
}
|
||||
if (startIdx < endIdx) {
|
||||
parseSanitizedCharBuffer(chars, data, startIdx, endIdx);
|
||||
}
|
||||
String firstPartBeforeDelim = chars.subSequence(0, startIdx + 1).toString();
|
||||
String lastPartBeforeDelim = chars.subSequence(endIdx + 1, chars.length()).toString();
|
||||
leftoversMap.put(key, firstPartBeforeDelim + lastPartBeforeDelim);
|
||||
chars = null;
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void parseSanitizedCharBuffer(CharSequence sequence, Map<String, double[]> data, int startIdx, int endIdx) {
|
||||
StringBuilder parseBuffer = new StringBuilder();
|
||||
String name = null;
|
||||
for (int i = startIdx; i < endIdx; ++i) {
|
||||
char c = sequence.charAt(i);
|
||||
if (c == '\r') {
|
||||
continue;
|
||||
}
|
||||
if (c == '\n') {
|
||||
if (parseBuffer.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
addParsedDataToMap(data, parseBuffer, name);
|
||||
parseBuffer.setLength(0);
|
||||
continue;
|
||||
}
|
||||
if (c == ';') {
|
||||
name = parseBuffer.toString();
|
||||
parseBuffer.setLength(0);
|
||||
continue;
|
||||
}
|
||||
parseBuffer.append(c);
|
||||
}
|
||||
if (!parseBuffer.isEmpty()) {
|
||||
assert name != null;
|
||||
addParsedDataToMap(data, parseBuffer, name);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addParsedDataToMap(Map<String, double[]> data, StringBuilder parseBuffer, String name) {
|
||||
String value = parseBuffer.toString();
|
||||
double valueNum = Double.parseDouble(value);
|
||||
double[] existingMeasurements = data.putIfAbsent(name, new double[]{ valueNum, valueNum, 1, valueNum });
|
||||
if (existingMeasurements != null) {
|
||||
existingMeasurements[0] = Math.min(existingMeasurements[0], valueNum);
|
||||
existingMeasurements[1] = Math.max(existingMeasurements[1], valueNum);
|
||||
++existingMeasurements[2];
|
||||
existingMeasurements[3] += valueNum;
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, double[]> readFile(File file) throws Exception {
|
||||
|
||||
Map<String, double[]> aggregate = new TreeMap<>(Comparator.naturalOrder());
|
||||
List<Future<Map<String, double[]>>> futures = new ArrayList<>();
|
||||
ConcurrentMap<Integer, String> leftoversMap = new ConcurrentSkipListMap<>();
|
||||
|
||||
Charset defaultCharset = Charset.defaultCharset();
|
||||
CharsetDecoder decoder = defaultCharset.newDecoder();
|
||||
|
||||
int bufferCapacity = 10 * 1024 * 1024;
|
||||
|
||||
long len = file.length();
|
||||
int idCounter = 0;
|
||||
try (
|
||||
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
FileChannel chan = FileChannel.open(file.toPath(), StandardOpenOption.READ)) {
|
||||
|
||||
ByteBuffer mainBuffer = ByteBuffer.allocate(bufferCapacity);
|
||||
int totalRead = 0;
|
||||
while (totalRead < len) {
|
||||
mainBuffer.clear();
|
||||
long read = chan.read(mainBuffer);
|
||||
if (read == -1) {
|
||||
break;
|
||||
}
|
||||
totalRead += read;
|
||||
mainBuffer.flip();
|
||||
CharBuffer chars = null;
|
||||
for (;;) {
|
||||
try {
|
||||
chars = decoder.decode(mainBuffer);
|
||||
break;
|
||||
}
|
||||
catch (CharacterCodingException e) {
|
||||
// keep reading byte by byte until a valid sequence is decoded
|
||||
mainBuffer.rewind();
|
||||
ByteBuffer nextByte = ByteBuffer.allocate(1);
|
||||
chan.read(nextByte);
|
||||
nextByte.flip();
|
||||
mainBuffer = ByteBuffer.allocate(mainBuffer.capacity() + 1).put(mainBuffer).put(nextByte);
|
||||
mainBuffer.flip();
|
||||
}
|
||||
}
|
||||
int nextId = idCounter++;
|
||||
CharBuffer finalChars = chars;
|
||||
futures.add(
|
||||
executor.submit(() -> parseLines(Integer.valueOf(nextId), finalChars, leftoversMap)));
|
||||
}
|
||||
}
|
||||
for (Future<Map<String, double[]>> future : futures) {
|
||||
Map<String, double[]> chunk = future.get();
|
||||
aggregate(chunk, aggregate);
|
||||
}
|
||||
String leftovers = String.join("", leftoversMap.values());
|
||||
parseSanitizedCharBuffer(leftovers, aggregate, 0, leftovers.length());
|
||||
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
private static void aggregate(Map<String, double[]> chunks, Map<String, double[]> aggregate) {
|
||||
for (Map.Entry<String, double[]> chunk : chunks.entrySet()) {
|
||||
String name = chunk.getKey();
|
||||
double[] chunkData = chunk.getValue();
|
||||
double[] aggregateData = aggregate.putIfAbsent(name, chunkData);
|
||||
if (aggregateData != null) {
|
||||
aggregateData[0] = Math.min(aggregateData[0], chunkData[0]);
|
||||
aggregateData[1] = Math.max(aggregateData[1], chunkData[1]);
|
||||
aggregateData[2] += chunkData[2];
|
||||
aggregateData[3] += chunkData[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void print(Map<String, double[]> dataMap) {
|
||||
System.out.print("{");
|
||||
for (Iterator<Map.Entry<String, double[]>> dataEntryIt = dataMap.entrySet().iterator(); dataEntryIt.hasNext();) {
|
||||
String entryOutput = format(dataEntryIt.next());
|
||||
System.out.print(entryOutput);
|
||||
if (dataEntryIt.hasNext()) {
|
||||
System.out.print(", ");
|
||||
}
|
||||
}
|
||||
System.out.println("}");
|
||||
}
|
||||
|
||||
private static double round(double value) {
|
||||
return Math.round(value * 10.0) / 10.0;
|
||||
}
|
||||
|
||||
private static String format(Map.Entry<String, double[]> entry) {
|
||||
double[] dataPoints = entry.getValue();
|
||||
double min = round(dataPoints[0]);
|
||||
double max = round(dataPoints[1]);
|
||||
double mean = round(dataPoints[3] / dataPoints[2]);
|
||||
return entry.getKey() + "=" + min + "/" + mean + "/" + max;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
File file = new File("./measurements.txt");
|
||||
Map<String, double[]> data = readFile(file);
|
||||
print(data);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user