Qui ho visto molte domande sulle prestazioni lambda di Java, ma la maggior parte di esse è simile a "Lambdas è leggermente più veloce, ma diventa più lento quando si usano chiusure" o "Warm-up vs i tempi di esecuzione sono diversi "o altre cose simili.Java lambdas 20 volte più lento delle classi anonime
Tuttavia, ho trovato una cosa piuttosto strana qui. Consideriamo this LeetCode problem:
Dato un insieme di intervalli non sovrapposti, inserire un nuovo intervallo in intervalli (merge se necessario).
Si può supporre che gli intervalli siano stati inizialmente ordinati in base allo loro orari di inizio.
Il problema è stato taggato con difficoltà, quindi ho assunto che un approccio lineare non è quello che vogliono lì. Così ho deciso di inventare un modo intelligente per combinare la ricerca binaria con le modifiche alla lista di input. Ora il problema non è molto chiaro sulla modifica della lista di input, dice "insert", anche se la firma richiede di restituire un riferimento alla lista, ma non importa per ora. Ecco il codice completo, ma solo le prime poche righe sono rilevanti per questa domanda. Sto mantenendo il resto qui solo in modo che chiunque può provare:
public List<Interval> insert(List<Interval> intervals, Interval newInterval) {
int start = Collections.binarySearch(intervals, newInterval,
(i1, i2) -> Integer.compare(i1.start, i2.start));
int skip = start >= 0 ? start : -start - 1;
int end = Collections.binarySearch(intervals.subList(skip, intervals.size()),
new Interval(newInterval.end, 0),
(i1, i2) -> Integer.compare(i1.start, i2.start));
if (end >= 0) {
end += skip; // back to original indexes
} else {
end -= skip; // ditto
}
int newStart = newInterval.start;
int headEnd;
if (-start - 2 >= 0) {
Interval prev = intervals.get(-start - 2);
if (prev.end < newInterval.start) {
// the new interval doesn't overlap the one before the insertion point
headEnd = -start - 1;
} else {
newStart = prev.start;
headEnd = -start - 2;
}
} else if (start >= 0) {
// merge the first interval
headEnd = start;
} else { // start == -1, insertion point = 0
headEnd = 0;
}
int newEnd = newInterval.end;
int tailStart;
if (-end - 2 >= 0) {
// merge the end with the previous interval
newEnd = Math.max(newEnd, intervals.get(-end - 2).end);
tailStart = -end - 1;
} else if (end >= 0) {
newEnd = intervals.get(end).end;
tailStart = end + 1;
} else { // end == -1, insertion point = 0
tailStart = 0;
}
intervals.subList(headEnd, tailStart).clear();
intervals.add(headEnd, new Interval(newStart, newEnd));
return intervals;
}
Questo ha funzionato bene, ma ho accettato, ma con 80 ms tempo di esecuzione, mentre la maggior parte delle soluzioni erano 4-5 ms e alcuni 18-19 ms. Quando li ho visti, erano tutti lineari e molto primitivi. Non qualcosa che ci si aspetterebbe da un problema taggato "difficile".
Ma ecco che arriva la domanda: la mia soluzione è anche lineare nel caso peggiore (perché le operazioni di aggiunta/cancellazione sono di tempo lineare). Perché è che è più lento? E poi ho fatto questo:
Comparator<Interval> comparator = new Comparator<Interval>() {
@Override
public int compare(Interval i1, Interval i2) {
return Integer.compare(i1.start, i2.start);
}
};
int start = Collections.binarySearch(intervals, newInterval, comparator);
int skip = start >= 0 ? start : -start - 1;
int end = Collections.binarySearch(intervals.subList(skip, intervals.size()),
new Interval(newInterval.end, 0),
comparator);
Da 80 ms fino a 4 ms! Cosa sta succedendo qui? Sfortunatamente non ho idea di che tipo di test esegua LeetCode o in quale ambiente, ma non è 20 volte troppo?
Avete ** ** ripetutamente eseguire questo metodo e tempo misurato? – Jan
Vedere http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java –
Non ho accesso ai test case, quindi non posso Eseguilo esattamente ripetutamente. Non so cosa faccia LeetCode per ottenere questa prestazione strana, quindi la domanda è questa: cosa potrebbe fare per renderlo così brutto? –