Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Long text in multiline word wrapped entries causes massive performance issues #5297

Open
2 tasks done
steampoweredtaco opened this issue Dec 5, 2024 · 4 comments
Open
2 tasks done
Labels
unverified A bug that has been reported but not verified

Comments

@steampoweredtaco
Copy link
Contributor

steampoweredtaco commented Dec 5, 2024

Checklist

  • I have searched the issue tracker for open issues that relate to the same problem, before opening a new one.
  • This issue only relates to a single bug. I will open new issues for any other problems.

Describe the bug

Text entry will cause a UI to become unresponsive for large periods of time when .Multiline = true and .Wrapping = fyne.TextWrapWord. This is always on initial use of the entry but it might be happening in other cases.

How to reproduce

Easy to demonstrate, create a fyne app where the content is a scroller with a multiline entry and .Wrapping set to fyne.TextWrapWord. See code example with details and nuance.

Screenshots

Desktop.2024.12.03.-.19.44.01.13.DVR_nm.mp4

Example code

package main

import (
	"fmt"
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/widget"
	"log"
	"math/rand"
	"os"
	"runtime/pprof"
	"time"
)

const (
	// At about 2K ~2s, 4K ~10s, 8K ~37-40s
	low  = 8000
	high = 8001
)

// This adds breakable characters as well to show it isn't about
// how long it takes to wrap but in general how much text there is.
func randString(low, high int) string {
	cnt := low + rand.Intn(high-low)
	s := make([]byte, cnt)
	for i := range s {
		s[i] = byte(rand.Intn('}'-' ') + ' ')
	}
	return string(s)
}

func main() {
	f, err := os.Create("cpuprof")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	pprof.StartCPUProfile(f)
	defer pprof.StopCPUProfile()
	a := app.New()
	w := a.NewWindow("slow render")
	w.Resize(fyne.NewSize(400, 400))
	e := widget.NewEntry()
	e.Text = randString(low, high)
	// If you comment out the next two lines there isn't an issue with
	// performance showing it has something to do with the multiline/wrap
	e.MultiLine = true
	e.Wrapping = fyne.TextWrapWord
	scroll := container.NewVScroll(e)
	// Adding the scroll content is enough to show the performance issue with large wrapped text
	n := time.Now()
	w.SetContent(scroll)
	fmt.Print("time to set content:", time.Since(n))
	// sometimes it takes the same amount of time to resize, but not always, left this out
	// w.Resize(fyne.NewSize(400, 400))
	go func() {
		time.Sleep(2 * time.Second)
		// Show no issues with resizing the window after initial load
		for i := 400.; i <= 800.; i += .5 {
			// not nearly as bad as setContent but still very slow and can start getting flickery at high counts
			w.Resize(fyne.NewSize(float32(i), 400))
		}
		w.Close()
	}()
	w.ShowAndRun()
}

Fyne version

v2.5.3-rc1 and v2.5.2 at least

Go compiler version

1.23.3

Operating system and version

Windows 10

Additional Information

At 8K characters it can take upwards of 40 seconds to set the content of a window with this entry inside of a scroll. It gets worst from there and you can easily change the example program to experiment.

This is the cpu profile at 8K characters. cpuprof.zip

A non trivial amount of time is spent in runtime slicerunetostring, but the majority seems to be throughout glDriver RenderTextSize stack and the harfbuzz algorithm.

In this video the first ~37 seconds waiting for it to load after calling .SetContent on the window. It also shows flickering/slowness of the program quickly trying to resize the width of the window containing a large entry. I am unsure if the resize slowness and flickering is related given the initial load takes so long.

@steampoweredtaco steampoweredtaco added the unverified A bug that has been reported but not verified label Dec 5, 2024
@dweymouth
Copy link
Contributor

This seems like a duplicate of #5059 to me

@dweymouth dweymouth closed this as not planned Won't fix, can't repro, duplicate, stale Dec 19, 2024
@dweymouth dweymouth added the duplicate This issue or pull request already exists label Dec 19, 2024
@andydotxyz
Copy link
Member

are you sure @dweymouth ? the issue you linked does not mention wrapping at all

@dweymouth
Copy link
Contributor

Maybe not? I figured it was the same root cause of Entry not being optimized for very large text, but I can reopen

@dweymouth dweymouth reopened this Dec 20, 2024
@beeblebrox
Copy link

beeblebrox commented Dec 23, 2024

Can you also remove the duplicate tag until it is verified to be the same root cause?

I don't think it is because most of the time is being spent repeatedly calculating rune sizes; maybe there is a performance issue with that is shared between the two issues, however it feels like there is some major O(N^2) time complexity going on when wrapping and recalculating over and over again.

@andydotxyz andydotxyz removed the duplicate This issue or pull request already exists label Dec 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
unverified A bug that has been reported but not verified
Projects
None yet
Development

No branches or pull requests

4 participants