Closed denandz closed 2 years ago
In my case, I don't really care about rune processing right now and I'm fairly happy just displaying bytes. I was able to fix my issue by tweaking TextView to hack up the string using slices instead.
Would setting a "raw" mode on a TextView be an option? I'd envisage some kind of flag where I could specify NewTextArea.SetRaw(true)
which would disable the rune processing and just slam bytes into the TextView with reckless abandon. This might be an easy win?
This was my hacky change to textview.go reindexBuffer
, just for reference:
if t.wrap && len(str) > 0 {
for len(str) > 0 {
var extract string
/* extract := runewidth.Truncate(str, width, "")
if len(extract) == 0 {
// We'll extract at least one grapheme cluster.
gr := uniseg.NewGraphemes(str)
gr.Next()
_, to := gr.Positions()
extract = str[:to]
}*/
if len(str) > width {
extract = str[:width]
} else {
extract = str
}
If raw mode was going to be implemented, then write
would also need to be tweaked to not do its partial trailing utf8 rune logic when raw mode is enabled.
If this is something you'd consider I can take a closer look and try get a PR together.
Another, less hacky option, would be to change the reindexBuffer
wrap logic to avoid the use of runewidth.Truncate
all together. I tested the following change with an 8MB textview buffer and did not experience the CPU consumption issue (I didn't test the word-wrap functionality, only wrap, FWIW):
for len(str) > 0 {
//extract := runewidth.Truncate(str, width, "")
var extract string
w := 0
for _, r := range str {
cw := runewidth.RuneWidth(r)
if w+cw > width {
break
}
extract += string(r)
w += cw
}
if len(extract) == 0 {
// We'll extract at least one grapheme cluster.
gr := uniseg.NewGraphemes(str)
gr.Next()
_, to := gr.Positions()
extract = str[:to]
}
if t.wordWrap && len(extract) < len(str) {
...yoink...
I'll preface my response by saying that I've mentioned multiple times in other issues that TextView
is not suitable for very large texts. You will always run into performance issues such as the one you encountered. I'm actually going to add a note about this in the official TextView
documentation because this comes up quite often.
As for your suggestions, I'm afraid that this will not be a route I will take. It all started like your first example. Then I quickly learned that it wasn't that simple. So I implemented your second example. And that wasn't enough either. (Check https://github.com/rivo/uniseg for an explanation.) Dozens of issues and complaints later, we're at an implementation that works with Unicode, different languages, wide characters, even emojis. Of course, all of these requirements from users come with a performance hit. If you find a way to make runewidth.Truncate()
faster, I think @mattn and I will be all ears. But tview
won't go back to the naive implementation. (And I currently don't intend to write and maintain an entire second implementation for "raw" mode.)
You may want to consider TextView.SetMaxLines()
to limit the number of lines in the text view. Or maybe use virtual tabes if random access is required. (But then you won't get word wrapping for free.)
Okay, if you don't intend to address the performance issues in TextView then you can close this issue WONTFIX.
TextView.SetMaxLines() doesn't work for my application, I'd like to be able to view 100 or 200kb of text in a text view without pinning my cpu.
I do have two requests though:
Actually, one other suggestion that just came to mind was that you might experience the high CPU load when you're redrawing the TextView
component very frequently. It's probably worth looking into reducing the number of redraws to maybe a few times per second. I haven't seen your implementation but if you update the screen after every io.Write()
to the TextView
, this might be a bit too much if only a few bytes are written to the text view at a time.
Finally, if your application really only deals with ASCII characters, it shouldn't be very difficult to implement line-breaking yourself and simply pipe that pre-broken-over text into the text view. Then you can turn off wrapping and runewidth.Truncate()
will never be called.
You can replicate this issue using the original code snippet in the original issue message, which has no calls to draw on each io.Write()
. It's fairly simple and should demonstrate the performance hits.
I can figure out a solution for my specific use case that doesn't involve changing anything in tview
In case anyone stumbles across this issue, I ended up implementing a very basic line wrapped text view primitive here: https://github.com/denandz/glorp/blob/master/views/textprimitive.go
Multi-megabyte buffers can be handled in a reasonable time frame, but none of the other TextView functionality is implemented.
Hello, I'm currently using tview for an interactive HTTP/HTTPS intercept proxy (https://github.com/denandz/glorp) and have come across an issue. In my case, i'd like to display large HTTP responses with word wrapping enabled, which causes issues.
If word wrapping is enabled on a TextView, and a large amount of data is written to the view, then a massive CPU consumption and application lockup until
TextView.reindexBuffer
returns.I found out this is due to
runewidth.Truncate
with large amounts of data. pprof output below, web attached.The following code can be used to replicate this issue, and was used to generate the profile above: