perf(gitdiff): optimize inline diff highlighting using cache (#38706)

This pull request optimizes code diff highlighting in the PR Files
Changed view, which is a major CPU bottleneck and hot path in
production.

Previously, Gitea executed the expensive `DiffMatchPatch` algorithm
twice for every matching deleted/added line pair (once for the deleted
line, and once for the added line). We now run the diff algorithm once
and cache the resulting `DiffInline` on the `DiffLine` struct itself,
allowing the second line to render via a 0-cost cache lookup.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
Shudhanshu Singh
2026-07-30 22:13:36 +05:30
committed by GitHub
parent 11d0ed699b
commit d87d26d735
11 changed files with 186 additions and 157 deletions
+1 -7
View File
@@ -10,14 +10,8 @@ type EscapeStatus struct {
HasAmbiguous bool
}
// Or combines two EscapeStatus structs into one representing the conjunction of the two
func (status *EscapeStatus) Or(other *EscapeStatus) *EscapeStatus {
st := status
if status == nil {
st = &EscapeStatus{}
}
func (st *EscapeStatus) Combine(other *EscapeStatus) {
st.Escaped = st.Escaped || other.Escaped
st.HasAmbiguous = st.HasAmbiguous || other.HasAmbiguous
st.HasInvisible = st.HasInvisible || other.HasInvisible
return st
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package highlight
import (
"strings"
"testing"
"github.com/alecthomas/chroma/v2/lexers"
)
func BenchmarkDetectChromaLexerByFileName(b *testing.B) {
for b.Loop() {
// BenchmarkDetectChromaLexerByFileName-12 18214717 61.35 ns/op
DetectChromaLexerByFileName("a.sql", "")
}
}
func BenchmarkDetectChromaLexerWithAnalyze(b *testing.B) {
code := []byte(strings.Repeat("SELECT * FROM table;\n", 1000))
b.ResetTimer()
for b.Loop() {
// BenchmarkRenderCodeSlowGuess-12 87946 13310 ns/op
detectChromaLexerWithAnalyze("a", "", code)
}
}
func BenchmarkChromaAnalyze(b *testing.B) {
code := strings.Repeat("SELECT * FROM table;\n", 1000)
b.ResetTimer()
for b.Loop() {
// comparing to detectChromaLexerWithAnalyze (go-enry), "chroma/lexers.Analyse" is very slow
// BenchmarkChromaAnalyze-12 519 2247104 ns/op
lexers.Analyse(code)
}
}
func BenchmarkRenderCodeByLexer(b *testing.B) {
code := strings.Repeat("SELECT * FROM table;\n", 1000)
lexer := DetectChromaLexerByFileName("a.sql", "")
b.ResetTimer()
for b.Loop() {
// HINT: CODE-HIGHLIGHT-PERFORMANCE: Really slow ....... the regexp2 used by Chroma takes most of the time
// BenchmarkRenderCodeByLexer-12 22 47159038 ns/op
RenderCodeByLexer(lexer, code)
}
}
-42
View File
@@ -4,53 +4,11 @@
package highlight
import (
"strings"
"testing"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/stretchr/testify/assert"
)
func BenchmarkDetectChromaLexerByFileName(b *testing.B) {
for b.Loop() {
// BenchmarkDetectChromaLexerByFileName-12 18214717 61.35 ns/op
DetectChromaLexerByFileName("a.sql", "")
}
}
func BenchmarkDetectChromaLexerWithAnalyze(b *testing.B) {
b.StopTimer()
code := []byte(strings.Repeat("SELECT * FROM table;\n", 1000))
b.StartTimer()
for b.Loop() {
// BenchmarkRenderCodeSlowGuess-12 87946 13310 ns/op
detectChromaLexerWithAnalyze("a", "", code)
}
}
func BenchmarkChromaAnalyze(b *testing.B) {
b.StopTimer()
code := strings.Repeat("SELECT * FROM table;\n", 1000)
b.StartTimer()
for b.Loop() {
// comparing to detectChromaLexerWithAnalyze (go-enry), "chroma/lexers.Analyse" is very slow
// BenchmarkChromaAnalyze-12 519 2247104 ns/op
lexers.Analyse(code)
}
}
func BenchmarkRenderCodeByLexer(b *testing.B) {
b.StopTimer()
code := strings.Repeat("SELECT * FROM table;\n", 1000)
lexer := DetectChromaLexerByFileName("a.sql", "")
b.StartTimer()
for b.Loop() {
// Really slow ....... the regexp2 used by Chroma takes most of the time
// BenchmarkRenderCodeByLexer-12 22 47159038 ns/op
RenderCodeByLexer(lexer, code)
}
}
func TestDetectChromaLexer(t *testing.T) {
globalVars().highlightMapping[".my-html"] = "HTML"
t.Cleanup(func() { delete(globalVars().highlightMapping, ".my-html") })