Skip to content

Commit

Permalink
Optimise SetContent
Browse files Browse the repository at this point in the history
* Remove reflect.DeepEqual call in SetContent
  > There's no need for reflection as we know the types.
  > Given tcell wants to stay compatible with older go versions, we now have
  > a small function that handles this, instead of using something like
  > `slices.Equal`.
* Add Simple benchmark to compare against the old versions
* Reuse `currComb` and `lastComb` slices to prevent allocations
  • Loading branch information
Bios-Marcel committed Jan 18, 2025
1 parent 7815866 commit 4ab7a00
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 4 deletions.
23 changes: 19 additions & 4 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package tcell

import (
"os"
"reflect"

runewidth "github.com/mattn/go-runewidth"
)
Expand Down Expand Up @@ -44,6 +43,21 @@ type CellBuffer struct {
cells []cell
}

// we purposefully don't use slices.Equal in order to stay compatible
// with earlier go versions.
func runeSliceEqual(a, b []rune) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}

return true
}

// SetContent sets the contents (primary rune, combining runes,
// and style) for a cell at a given location. If the background or
// foreground of the style is set to ColorNone, then the respective
Expand All @@ -58,13 +72,14 @@ func (cb *CellBuffer) SetContent(x int, y int,
// dirty as well as the base cell, to make sure we consider
// both cells as dirty together. We only need to do this
// if we're changing content
if (c.width > 0) && (mainc != c.currMain || len(combc) != len(c.currComb) || (len(combc) > 0 && !reflect.DeepEqual(combc, c.currComb))) {
if (c.width > 0) && (mainc != c.currMain || !runeSliceEqual(combc, c.currComb)) {
for i := 0; i < c.width; i++ {
cb.SetDirty(x+i, y, true)
}
}

c.currComb = append([]rune{}, combc...)
// Reuse slice to prevent allocations
c.currComb = append(c.currComb[:0], combc...)

if c.currMain != mainc {
c.width = runewidth.RuneWidth(mainc)
Expand Down Expand Up @@ -155,7 +170,7 @@ func (cb *CellBuffer) SetDirty(x, y int, dirty bool) {
c.currMain = ' '
}
c.lastMain = c.currMain
c.lastComb = c.currComb
c.lastComb = append(c.lastComb[:0], c.currComb...)
c.lastStyle = c.currStyle
}
}
Expand Down
80 changes: 80 additions & 0 deletions cell_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package tcell

import (
"reflect"
"testing"

runewidth "github.com/mattn/go-runewidth"
)

// SetContent sets the contents (primary rune, combining runes,
// and style) for a cell at a given location. If the background or
// foreground of the style is set to ColorNone, then the respective
// color is left un changed.
func (cb *CellBuffer) SetContentOld(x int, y int,
mainc rune, combc []rune, style Style,
) {
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
c := &cb.cells[(y*cb.w)+x]

// Wide characters: we want to mark the "wide" cells
// dirty as well as the base cell, to make sure we consider
// both cells as dirty together. We only need to do this
// if we're changing content
if (c.width > 0) && (mainc != c.currMain || len(combc) != len(c.currComb) || (len(combc) > 0 && !reflect.DeepEqual(combc, c.currComb))) {
for i := 0; i < c.width; i++ {
cb.SetDirty(x+i, y, true)
}
}

c.currComb = append([]rune{}, combc...)

if c.currMain != mainc {
c.width = runewidth.RuneWidth(mainc)
}
c.currMain = mainc
if style.fg == ColorNone {
style.fg = c.currStyle.fg
}
if style.bg == ColorNone {
style.bg = c.currStyle.bg
}
c.currStyle = style
}
}

func Benchmark_SetContentOld(b *testing.B) {
buffer := &CellBuffer{}
buffer.Resize(100, 100)
flag := []rune("🇦🇺")
for i := 0; i < b.N; i++ {
for w := 0; w < 100; w++ {
for h := 0; h < 100; h++ {
buffer.SetContentOld(w, h, 'a', nil, StyleDefault)
}
}
for w := 0; w < 100; w++ {
for h := 0; h < 100; h++ {
buffer.SetContentOld(w, h, flag[0], flag[1:], StyleDefault)
}
}
}
}

func Benchmark_SetContent(b *testing.B) {
buffer := &CellBuffer{}
buffer.Resize(100, 100)
flag := []rune("🇦🇺")
for i := 0; i < b.N; i++ {
for w := 0; w < 100; w++ {
for h := 0; h < 100; h++ {
buffer.SetContent(w, h, 'a', nil, StyleDefault)
}
}
for w := 0; w < 100; w++ {
for h := 0; h < 100; h++ {
buffer.SetContent(w, h, flag[0], flag[1:], StyleDefault)
}
}
}
}

0 comments on commit 4ab7a00

Please sign in to comment.