From 48da545e324efd1ba0cb67e4a5fb129e06fa622b Mon Sep 17 00:00:00 2001 From: NiLuJe Date: Thu, 23 Sep 2021 17:13:18 +0200 Subject: [PATCH] Kobo/Elipsa: More fine-grained control over the amount of online CPU cores * Only keep a single core online most of the time. * Device: Add an enableCPUCores method to allow controlling the amount of online CPU cores. * Move the initial core onlining setup to Kobo:init, instead of the startup script. * Enable two CPU cores while hinting new (e.g., cache miss) pages in PDF land. * Enable two CPU cores while processing book metadata. * Drive-by fix to isolate the DocCache pressure check to KoptInterface and actually apply it when it matters most (e.g., k2pdfopt stuff). --- frontend/device/generic/device.lua | 4 ++ frontend/device/kobo/device.lua | 68 ++++++++++++++++--- frontend/document/djvudocument.lua | 4 +- frontend/document/document.lua | 14 ++-- frontend/document/koptinterface.lua | 53 +++++++++++---- frontend/document/pdfdocument.lua | 4 +- platform/kobo/koreader.sh | 9 --- .../coverbrowser.koplugin/bookinfomanager.lua | 8 +++ 8 files changed, 126 insertions(+), 38 deletions(-) diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index bec361e0b..4d0f40f82 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -460,6 +460,10 @@ end -- Device specific method for toggling the charging LED function Device:toggleChargingLED(toggle) end +-- Device specific method for enabling a specific amount of CPU cores +-- (Should only be implemented on embedded platforms where we can afford to control that without screwing with the system). +function Device:enableCPUCores(amount) end + --[[ prepare for application shutdown --]] diff --git a/frontend/device/kobo/device.lua b/frontend/device/kobo/device.lua index 1fe0fa4a9..9e49b1ce0 100644 --- a/frontend/device/kobo/device.lua +++ b/frontend/device/kobo/device.lua @@ -71,6 +71,8 @@ local Kobo = Generic:new{ touch_dev = "/dev/input/event1", -- Event code to use to detect contact pressure pressure_event = nil, + -- Device features multiple CPU cores + isSMP = no, } --- @todo hasKeys for some devices? @@ -321,6 +323,7 @@ local KoboEuropa = Kobo:new{ battery_sysfs = "/sys/class/power_supply/battery", ntx_dev = "/dev/input/by-path/platform-ntx_event0-event", touch_dev = "/dev/input/by-path/platform-0-0010-event", + isSMP = yes, } function Kobo:init() @@ -439,6 +442,9 @@ function Kobo:init() -- * Turn it off on startup -- I've chosen the latter, as I find it vaguely saner, more useful, and it matches Nickel's behavior (I think). self:toggleChargingLED(false) + + -- Only enable a single core on startup + self:enableCPUCores(1) end function Kobo:setDateTime(year, month, day, hour, min, sec) @@ -657,7 +663,7 @@ function Kobo:suspend() local has_wakeup_count = false f = io.open("/sys/power/wakeup_count", "r") if f ~= nil then - io.close(f) + f:close() has_wakeup_count = true end @@ -682,7 +688,7 @@ function Kobo:suspend() return false end re, err_msg, err_code = f:write("1\n") - io.close(f) + f:close() logger.info("Kobo suspend: asked the kernel to put subsystems to sleep, ret:", re) if not re then logger.err('write error: ', err_msg, err_code) @@ -709,7 +715,7 @@ function Kobo:suspend() err_msg, err_code) end - io.close(f) + f:close() end --]] @@ -723,7 +729,7 @@ function Kobo:suspend() logger.err("cannot open /sys/power/state-extended for writing!") else ext_fd:write("0\n") - io.close(ext_fd) + ext_fd:close() end return false end @@ -735,7 +741,7 @@ function Kobo:suspend() if not re then logger.err('write error: ', err_msg, err_code) end - io.close(f) + f:close() -- NOTE: Ideally, we'd need a way to warn the user that suspending -- gloriously failed at this point... -- We can safely assume that just from a non-zero return code, without @@ -786,7 +792,7 @@ function Kobo:resume() return false end local re, err_msg, err_code = f:write("0\n") - io.close(f) + f:close() logger.info("Kobo resume: unflagged kernel subsystems for resume, ret:", re) if not re then logger.err('write error: ', err_msg, err_code) @@ -799,7 +805,7 @@ function Kobo:resume() f = io.open("/sys/devices/virtual/input/input1/neocmd", "w") if f ~= nil then f:write("a\n") - io.close(f) + f:close() end end @@ -892,7 +898,53 @@ function Kobo:toggleChargingLED(toggle) f:flush() end - io.close(f) + f:close() +end + +-- Return the highest core number +local function getCPUCount() + local fd = io.open("/sys/devices/system/cpu/possible", "r") + if fd then + local str = fd:read("*line") + fd:close() + + -- Format is n-N, where n is the first core, and N the last (e.g., 0-3) + return tonumber(str:match("%d+$")) or 0 + else + return 0 + end +end + +function Kobo:enableCPUCores(amount) + if not self:isSMP() then + return + end + + if not self.cpu_count then + self.cpu_count = getCPUCount() + end + + -- Not actually SMP or getCPUCount failed... + if self.cpu_count == 0 then + return + end + + -- CPU0 is *always* online ;). + for n=1, self.cpu_count do + local path = "/sys/devices/system/cpu/cpu" .. n .. "/online" + local up + if n >= amount then + up = "0" + else + up = "1" + end + + local f = io.open(path, "w") + if f then + f:write(up) + f:close() + end + end end function Kobo:isStartupScriptUpToDate() diff --git a/frontend/document/djvudocument.lua b/frontend/document/djvudocument.lua index a0ca5366d..186bac8c3 100644 --- a/frontend/document/djvudocument.lua +++ b/frontend/document/djvudocument.lua @@ -136,8 +136,8 @@ function DjvuDocument:findText(pattern, origin, reverse, caseInsensitive, page) return self.koptinterface:findText(self, pattern, origin, reverse, caseInsensitive, page) end -function DjvuDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mode) - return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, gamma, render_mode) +function DjvuDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mode, hinting) + return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, gamma, render_mode, hinting) end function DjvuDocument:hintPage(pageno, zoom, rotation, gamma, render_mode) diff --git a/frontend/document/document.lua b/frontend/document/document.lua index 44a8c0d1f..7b84e336e 100644 --- a/frontend/document/document.lua +++ b/frontend/document/document.lua @@ -1,6 +1,7 @@ local Blitbuffer = require("ffi/blitbuffer") local CacheItem = require("cacheitem") local Configurable = require("configurable") +local Device = require("device") local DocCache = require("document/doccache") local DrawContext = require("ffi/drawcontext") local CanvasContext = require("document/canvascontext") @@ -392,7 +393,7 @@ function Document:getFullPageHash(pageno, zoom, rotation, gamma, render_mode, co ..(self.reflowable_font_size and "|"..self.reflowable_font_size or "") end -function Document:renderPage(pageno, rect, zoom, rotation, gamma, render_mode) +function Document:renderPage(pageno, rect, zoom, rotation, gamma, render_mode, hinting) local hash_excerpt local hash = self:getFullPageHash(pageno, zoom, rotation, gamma, render_mode, self.render_color) local tile = DocCache:check(hash, TileCacheItem) @@ -411,6 +412,9 @@ function Document:renderPage(pageno, rect, zoom, rotation, gamma, render_mode) end end + if hinting then + Device:enableCPUCores(2) + end self:preRenderPage() local page_size = self:getPageDimensions(pageno, zoom, rotation) @@ -466,17 +470,17 @@ function Document:renderPage(pageno, rect, zoom, rotation, gamma, render_mode) DocCache:insert(hash, tile) self:postRenderPage() + if hinting then + Device:enableCPUCores(1) + end return tile end -- a hint for the cache engine to paint a full page to the cache --- @todo this should trigger a background operation function Document:hintPage(pageno, zoom, rotation, gamma, render_mode) - --- @note: Crappy safeguard around memory issues like in #7627: if we're eating too much RAM, drop half the cache... - DocCache:memoryPressureCheck() - logger.dbg("hinting page", pageno) - self:renderPage(pageno, nil, zoom, rotation, gamma, render_mode) + self:renderPage(pageno, nil, zoom, rotation, gamma, render_mode, true) end --[[ diff --git a/frontend/document/koptinterface.lua b/frontend/document/koptinterface.lua index 17b891157..c15ea7e4e 100644 --- a/frontend/document/koptinterface.lua +++ b/frontend/document/koptinterface.lua @@ -6,6 +6,7 @@ local CacheItem = require("cacheitem") local CanvasContext = require("document/canvascontext") local DataStorage = require("datastorage") local DEBUG = require("dbg") +local Device = require("device") local DocCache = require("document/doccache") local Document = require("document/document") local Geom = require("ui/geometry") @@ -99,12 +100,20 @@ function KoptInterface:setDefaultConfigurable(configurable) end function KoptInterface:waitForContext(kc) - -- if koptcontext is being processed in background thread - -- the isPreCache will return 1. + -- If our koptcontext is busy in a background thread, isPreCache will return 1. + local waited = false while kc and kc:isPreCache() == 1 do + waited = true logger.dbg("waiting for background rendering") util.usleep(100000) end + + if waited or self.bg_thread then + -- Background thread is done, go back to a single CPU core. + Device:enableCPUCores(1) + self.bg_thread = nil + end + return kc end @@ -266,7 +275,7 @@ function KoptInterface:getCachedContext(doc, pageno) logger.dbg("reflowing page", pageno, "in foreground") -- reflow page --local secs, usecs = util.gettime() - page:reflow(kc, 0) + page:reflow(kc) page:close() --local nsecs, nusecs = util.gettime() --local dur = nsecs - secs + (nusecs - usecs) / 1000000 @@ -322,13 +331,13 @@ function KoptInterface:getCoverPageImage(doc) end end -function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, gamma, render_mode) +function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, gamma, render_mode, hinting) if doc.configurable.text_wrap == 1 then - return self:renderReflowedPage(doc, pageno, rect, zoom, rotation, render_mode) + return self:renderReflowedPage(doc, pageno, rect, zoom, rotation, render_mode, hinting) elseif doc.configurable.page_opt == 1 then - return self:renderOptimizedPage(doc, pageno, rect, zoom, rotation, render_mode) + return self:renderOptimizedPage(doc, pageno, rect, zoom, rotation, render_mode, hinting) else - return Document.renderPage(doc, pageno, rect, zoom, rotation, gamma, render_mode) + return Document.renderPage(doc, pageno, rect, zoom, rotation, gamma, render_mode, hinting) end end @@ -372,7 +381,7 @@ Render optimized page into tile cache. Inherited from common document interface. --]] -function KoptInterface:renderOptimizedPage(doc, pageno, rect, zoom, rotation, render_mode) +function KoptInterface:renderOptimizedPage(doc, pageno, rect, zoom, rotation, render_mode, hinting) doc.render_mode = render_mode local bbox = doc:getPageBBox(pageno) local hash_list = { "renderoptpg" } @@ -381,6 +390,10 @@ function KoptInterface:renderOptimizedPage(doc, pageno, rect, zoom, rotation, re local cached = DocCache:check(hash, TileCacheItem) if not cached then + if hinting then + Device:enableCPUCores(2) + end + local page_size = Document.getNativePageDimensions(doc, pageno) local full_page_bbox = { x0 = 0, y0 = 0, @@ -409,6 +422,11 @@ function KoptInterface:renderOptimizedPage(doc, pageno, rect, zoom, rotation, re tile.size = tonumber(tile.bb.stride) * tile.bb.h + 512 -- estimation kc:free() DocCache:insert(hash, tile) + + if hinting then + Device:enableCPUCores(1) + end + return tile else return cached @@ -416,10 +434,13 @@ function KoptInterface:renderOptimizedPage(doc, pageno, rect, zoom, rotation, re end function KoptInterface:hintPage(doc, pageno, zoom, rotation, gamma, render_mode) + --- @note: Crappy safeguard around memory issues like in #7627: if we're eating too much RAM, drop half the cache... + DocCache:memoryPressureCheck() + if doc.configurable.text_wrap == 1 then - self:hintReflowedPage(doc, pageno, zoom, rotation, gamma, render_mode) + self:hintReflowedPage(doc, pageno, zoom, rotation, gamma, render_mode, true) elseif doc.configurable.page_opt == 1 then - self:renderOptimizedPage(doc, pageno, nil, zoom, rotation, gamma, render_mode) + self:renderOptimizedPage(doc, pageno, nil, zoom, rotation, gamma, render_mode, true) else Document.hintPage(doc, pageno, zoom, rotation, gamma, render_mode) end @@ -434,24 +455,32 @@ off by calling self:waitForContext(kctx) Inherited from common document interface. --]] -function KoptInterface:hintReflowedPage(doc, pageno, zoom, rotation, gamma, render_mode) +function KoptInterface:hintReflowedPage(doc, pageno, zoom, rotation, gamma, render_mode, hinting) local bbox = doc:getPageBBox(pageno) local hash_list = { "kctx" } self:getContextHash(doc, pageno, bbox, hash_list) local hash = table.concat(hash_list, "|") local cached = DocCache:check(hash) if not cached then + if hinting then + Device:enableCPUCores(2) + end + local kc = self:createContext(doc, pageno, bbox) local page = doc._document:openPage(pageno) logger.dbg("hinting page", pageno, "in background") -- reflow will return immediately and running in background thread kc:setPreCache() - page:reflow(kc, 0) + self.bg_thread = true + page:reflow(kc) page:close() DocCache:insert(hash, ContextCacheItem:new{ size = self.last_context_size or self.default_context_size, kctx = kc, }) + + -- We'll wait until the background thread is done to go back to a single core, as this returns immediately! + -- c.f., :waitForContext end end diff --git a/frontend/document/pdfdocument.lua b/frontend/document/pdfdocument.lua index 97be49205..90620cde9 100644 --- a/frontend/document/pdfdocument.lua +++ b/frontend/document/pdfdocument.lua @@ -340,8 +340,8 @@ function PdfDocument:findText(pattern, origin, reverse, caseInsensitive, page) return self.koptinterface:findText(self, pattern, origin, reverse, caseInsensitive, page) end -function PdfDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mode) - return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, gamma, render_mode) +function PdfDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mode, hinting) + return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, gamma, render_mode, hinting) end function PdfDocument:hintPage(pageno, zoom, rotation, gamma, render_mode) diff --git a/platform/kobo/koreader.sh b/platform/kobo/koreader.sh index f40a49789..1514863fc 100755 --- a/platform/kobo/koreader.sh +++ b/platform/kobo/koreader.sh @@ -262,15 +262,6 @@ else KOBO_TS_INPUT="/dev/input/event1" fi -# Make sure we only keep two cores online on the Elipsa. -# NOTE: That's a bit optimistic, we might actually need to tone that down to one, -# and just toggle the second one on demand (e.g., PDF). -if [ "${PRODUCT}" = "europa" ]; then - echo "1" >"/sys/devices/system/cpu/cpu1/online" - echo "0" >"/sys/devices/system/cpu/cpu2/online" - echo "0" >"/sys/devices/system/cpu/cpu3/online" -fi - # We'll want to ensure Portrait rotation to allow us to use faster blitting codepaths @ 8bpp, # so remember the current one before fbdepth does its thing. IFS= read -r ORIG_FB_ROTA <"/sys/class/graphics/fb0/rotate" diff --git a/plugins/coverbrowser.koplugin/bookinfomanager.lua b/plugins/coverbrowser.koplugin/bookinfomanager.lua index c95c17105..59e1e3792 100644 --- a/plugins/coverbrowser.koplugin/bookinfomanager.lua +++ b/plugins/coverbrowser.koplugin/bookinfomanager.lua @@ -659,6 +659,9 @@ function BookInfoManager:collectSubprocesses() end end end + + -- We're done, back to a single core + Device:enableCPUCores(1) end function BookInfoManager:terminateBackgroundJobs() @@ -698,6 +701,11 @@ function BookInfoManager:extractInBackground(files) self.cleanup_needed = true -- so we will remove temporary cache directory created by subprocess + -- If it's the first subprocess we're launching, enable 2 CPU cores + if #self.subprocesses_pids == 0 then + Device:enableCPUCores(2) + end + -- Run task in sub-process, and remember its pid local task_pid = FFIUtil.runInSubProcess(task) if not task_pid then