Ich habe eine Menge Fragen über Java Lambdas Leistung gesehen, aber die meisten von ihnen gehen wie "Lambdas sind etwas schneller, aber langsamer bei der Verwendung von Verschlüsse" oder "Warm-up vs Ausführungszeiten sind anders "oder andere solche Dinge.Java Lambdas 20 mal langsamer als anonyme Klassen
Allerdings traf ich hier eine ziemlich seltsame Sache. Betrachten this LeetCode problem:
einen Satz von nicht überlappenden Intervallen gegeben, legt ein neues Intervall in die Intervalle (merge falls erforderlich).
Sie können davon ausgehen, dass die Intervalle zunächst nach ihren Startzeiten sortiert wurden.
Das Problem wurde hart markiert, also nahm ich an, dass ein linearer Ansatz nicht das ist, was sie dort wollen. Also entschied ich mich, eine clevere Möglichkeit zu finden, die binäre Suche mit Änderungen an der Eingabeliste zu kombinieren. Nun ist das Problem beim Ändern der Eingabeliste nicht sehr klar - es heißt "Einfügen", obwohl die Signatur einen Verweis auf die Liste zurückgeben muss, aber das ist für jetzt gleichgültig. Hier ist der vollständige Code, aber nur die ersten Zeilen sind für diese Frage relevant. Ich behalte den Rest hier einfach so, dass jeder kann es versuchen:
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;
}
Dies funktionierte gut und wurde angenommen, aber mit 80 ms Laufzeit, während die meisten Lösungen waren 4-5 ms und einige 18-19 ms. Als ich nach ihnen suchte, waren sie alle linear und sehr primitiv. Nicht etwas, was man von einem "hart" genannten Problem erwarten würde.
Aber hier kommt die Frage: meine Lösung ist auch im schlimmsten Fall linear (weil add/clear Operationen lineare Zeit sind). Warum ist es dass langsamer? Und dann tat ich das:
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);
Von 80 ms bis zu 4 ms! Was ist denn hier los? Leider habe ich keine Ahnung, welche Tests LeetCode unter welcher Umgebung durchführt, aber trotzdem ist es nicht 20 mal zu viel?
Haben Sie ** ** wiederholt diese Methode und gemessene Zeit laufen? – Jan
Siehe http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java –
Ich habe keinen Zugriff auf die Testfälle, so kann ich nicht wiederhole es genau. Ich weiß nicht, was LeetCode tut, um diese seltsame Leistung zu erreichen, also ist die Frage: Was kann es tun, um es so schlecht zu machen? –