From 164572ca1eb022a90afbe478c9df5884cd936737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 3 Oct 2019 14:37:48 +0200 Subject: [PATCH] [apps/shared][apps/sequence][apps/graph] Speed-up values table scrolling by memoizing values cell buffers --- apps/graph/values/values_controller.cpp | 45 +++++++++++-- apps/graph/values/values_controller.h | 18 +++++- apps/sequence/values/values_controller.cpp | 8 ++- apps/sequence/values/values_controller.h | 11 +++- apps/shared/values_controller.cpp | 74 ++++++++++++++++++++-- apps/shared/values_controller.h | 37 ++++++++++- 6 files changed, 174 insertions(+), 19 deletions(-) diff --git a/apps/graph/values/values_controller.cpp b/apps/graph/values/values_controller.cpp index 579ea5e7d..3922be41d 100644 --- a/apps/graph/values/values_controller.cpp +++ b/apps/graph/values/values_controller.cpp @@ -231,11 +231,28 @@ ViewController * ValuesController::functionParameterController() { return &m_functionParameterController; } -void ValuesController::printEvaluationOfAbscissaAtColumn(double abscissa, int columnIndex, char * buffer, const int bufferSize) { +int ValuesController::valuesColumnForAbsoluteColumn(int column) { + return column - numberOfAbscissaColumnsBeforeColumn(column); +} + +int ValuesController::absoluteColumnForValuesColumn(int column) { + int abscissaColumns = 0; + int valuesColumns = 0; + int plotTypeIndex = 0; + do { + abscissaColumns++; + assert(plotTypeIndex < Shared::ContinuousFunction::k_numberOfPlotTypes); + valuesColumns += m_numberOfValuesColumnsForType[plotTypeIndex++]; + } while (valuesColumns <= column); + return column + abscissaColumns; +} + +void ValuesController::fillMemoizedBuffer(int column, int row, int index) { + double abscissa = intervalAtColumn(column)->element(row-1); bool isDerivative = false; double evaluationX = NAN; double evaluationY = NAN; - Ion::Storage::Record record = recordAtColumn(columnIndex, &isDerivative); + Ion::Storage::Record record = recordAtColumn(column, &isDerivative); Shared::ExpiringPointer function = functionStore()->modelForRecord(record); Poincare::Context * context = textFieldDelegateApp()->localContext(); bool isParametric = function->plotType() == ContinuousFunction::PlotType::Parametric; @@ -248,17 +265,18 @@ void ValuesController::printEvaluationOfAbscissaAtColumn(double abscissa, int co evaluationX = eval.x1(); } } + char * buffer = memoizedBufferAtIndex(index); int numberOfChar = 0; if (isParametric) { - assert(numberOfChar < bufferSize-1); + assert(numberOfChar < k_valuesCellBufferSize-1); buffer[numberOfChar++] = '('; - numberOfChar += PoincareHelpers::ConvertFloatToText(evaluationX, buffer+numberOfChar, bufferSize - numberOfChar, Preferences::LargeNumberOfSignificantDigits); - assert(numberOfChar < bufferSize-1); + numberOfChar += PoincareHelpers::ConvertFloatToText(evaluationX, buffer+numberOfChar, k_valuesCellBufferSize - numberOfChar, Preferences::LargeNumberOfSignificantDigits); + assert(numberOfChar < k_valuesCellBufferSize-1); buffer[numberOfChar++] = ';'; } - numberOfChar += PoincareHelpers::ConvertFloatToText(evaluationY, buffer+numberOfChar, bufferSize - numberOfChar, Preferences::LargeNumberOfSignificantDigits); + numberOfChar += PoincareHelpers::ConvertFloatToText(evaluationY, buffer+numberOfChar, k_valuesCellBufferSize - numberOfChar, Preferences::LargeNumberOfSignificantDigits); if (isParametric) { - assert(numberOfChar+1 < bufferSize-1); + assert(numberOfChar+1 < k_valuesCellBufferSize-1); buffer[numberOfChar++] = ')'; buffer[numberOfChar] = 0; } @@ -290,6 +308,19 @@ int ValuesController::numberOfColumnsForPlotType(int plotTypeIndex) const { return m_numberOfValuesColumnsForType[plotTypeIndex] + (m_numberOfValuesColumnsForType[plotTypeIndex] > 0); // Count abscissa column if there is one } +int ValuesController::numberOfAbscissaColumnsBeforeColumn(int column) { + int result = 0; + int plotType = column < 0 ? Shared::ContinuousFunction::k_numberOfPlotTypes : (int)plotTypeAtColumn(&column) + 1; + for (int plotTypeIndex = 0; plotTypeIndex < plotType; plotTypeIndex++) { + result += (m_numberOfValuesColumnsForType[plotTypeIndex] > 0); + } + return result; +} + +int ValuesController::numberOfValuesColumns() { + return m_numberOfColumns - numberOfAbscissaColumnsBeforeColumn(-1); +} + int writeMatrixBrakets(char * buffer, const int bufferSize, int type) { /* Write the double brackets required in matrix notation. * - type == 1: "[[" diff --git a/apps/graph/values/values_controller.h b/apps/graph/values/values_controller.h index d39f4d1c0..2930d8c4b 100644 --- a/apps/graph/values/values_controller.h +++ b/apps/graph/values/values_controller.h @@ -41,6 +41,19 @@ private: constexpr static int k_maxNumberOfAbscissaCells = Shared::ContinuousFunction::k_numberOfPlotTypes * k_maxNumberOfRows; constexpr static int k_maxNumberOfCells = k_maxNumberOfFunctions * k_maxNumberOfRows; + // Function evaluation memoization + char * memoizedBufferAtIndex(int i) override { + assert(i >= 0 && i < k_maxNumberOfCells); + return m_memoizedBuffer[i]; + } + int numberOfMemoizedColumn() override { return k_maxNumberOfFunctions; } + /* The conversion of column coordinates from the absolute table to the table + * on only values cell depends on the number of abscissa columns which depends + * on the number of different plot types in the table. */ + int valuesColumnForAbsoluteColumn(int column) override; + int absoluteColumnForValuesColumn(int column) override; + void fillMemoizedBuffer(int i, int j, int index) override; + void setStartEndMessages(Shared::IntervalParameterController * controller, int column) override; void updateNumberOfColumns() const override; Ion::Storage::Record recordAtColumn(int i) override; @@ -54,7 +67,6 @@ private: int maxNumberOfCells() override; int maxNumberOfFunctions() override; Shared::Hideable * hideableCellFromType(HighlightCell * cell, int type); - void printEvaluationOfAbscissaAtColumn(double abscissa, int columnIndex, char * buffer, const int bufferSize) override; ContinuousFunctionStore * functionStore() const override { return static_cast(Shared::ValuesController::functionStore()); } Shared::BufferFunctionTitleCell * functionTitleCells(int j) override; EvenOddBufferTextCell * floatCells(int j) override; @@ -65,6 +77,8 @@ private: ViewController * functionParameterController() override; SelectableTableView * selectableTableView() override { return &m_selectableTableView; } int numberOfColumnsForPlotType(int plotTypeIndex) const; + int numberOfAbscissaColumnsBeforeColumn(int column); + int numberOfValuesColumns() override; /* For parametric function, we display the evaluation with the form "(1;2)". * This form is not parsable so when we store it into the clipboard, we want @@ -88,6 +102,8 @@ private: IntervalParameterSelectorController m_intervalParameterSelectorController; DerivativeParameterController m_derivativeParameterController; Button m_setIntervalButton; + // TODO specialize buffer size as well + mutable char m_memoizedBuffer[k_maxNumberOfCells][k_valuesCellBufferSize]; }; } diff --git a/apps/sequence/values/values_controller.cpp b/apps/sequence/values/values_controller.cpp index 16c5f546a..d6f7fe14d 100644 --- a/apps/sequence/values/values_controller.cpp +++ b/apps/sequence/values/values_controller.cpp @@ -71,10 +71,12 @@ bool ValuesController::setDataAtLocation(double floatBody, int columnIndex, int return Shared::ValuesController::setDataAtLocation(std::round(floatBody), columnIndex, rowIndex); } -void ValuesController::printEvaluationOfAbscissaAtColumn(double abscissa, int columnIndex, char * buffer, const int bufferSize) { - Shared::ExpiringPointer sequence = functionStore()->modelForRecord(recordAtColumn(columnIndex)); +void ValuesController::fillMemoizedBuffer(int column, int row, int index) { + char * buffer = memoizedBufferAtIndex(index); + double abscissa = intervalAtColumn(column)->element(row-1); + Shared::ExpiringPointer sequence = functionStore()->modelForRecord(recordAtColumn(column)); Coordinate2D xy = sequence->evaluateXYAtParameter(abscissa, textFieldDelegateApp()->localContext()); - Shared::PoincareHelpers::ConvertFloatToText(xy.x2(), buffer, bufferSize, Preferences::LargeNumberOfSignificantDigits); + Shared::PoincareHelpers::ConvertFloatToText(xy.x2(), buffer, k_valuesCellBufferSize, Preferences::LargeNumberOfSignificantDigits); } Shared::Interval * ValuesController::intervalAtColumn(int columnIndex) { diff --git a/apps/sequence/values/values_controller.h b/apps/sequence/values/values_controller.h index 403c55ec4..f1b516e6c 100644 --- a/apps/sequence/values/values_controller.h +++ b/apps/sequence/values/values_controller.h @@ -23,11 +23,19 @@ public: private: void setStartEndMessages(Shared::IntervalParameterController * controller, int column) override; bool setDataAtLocation(double floatBody, int columnIndex, int rowIndex) override; - void printEvaluationOfAbscissaAtColumn(double abscissa, int columnIndex, char * buffer, const int bufferSize) override; Shared::Interval * intervalAtColumn(int columnIndex) override; I18n::Message valuesParameterMessageAtColumn(int columnIndex) const override; int maxNumberOfCells() override { return k_maxNumberOfCells; } int maxNumberOfFunctions() override { return k_maxNumberOfSequences; } + + // Function evaluation memoization + char * memoizedBufferAtIndex(int i) override { + assert(i >= 0 && i < k_maxNumberOfCells); + return m_memoizedBuffer[i]; + } + int numberOfMemoizedColumn() override { return k_maxNumberOfSequences; } + void fillMemoizedBuffer(int i, int j, int index) override; + constexpr static int k_maxNumberOfSequences = 3; constexpr static int k_maxNumberOfCells = k_maxNumberOfSequences * k_maxNumberOfRows; @@ -62,6 +70,7 @@ private: #endif IntervalParameterController m_intervalParameterController; Button m_setIntervalButton; + mutable char m_memoizedBuffer[k_maxNumberOfCells][k_valuesCellBufferSize]; }; } diff --git a/apps/shared/values_controller.cpp b/apps/shared/values_controller.cpp index 2528e2356..3842d0cac 100644 --- a/apps/shared/values_controller.cpp +++ b/apps/shared/values_controller.cpp @@ -2,16 +2,21 @@ #include "function_app.h" #include #include +#include using namespace Poincare; namespace Shared { +static inline int minInt(int x, int y) { return x < y ? x : y; } + ValuesController::ValuesController(Responder * parentResponder, ButtonRowController * header) : EditableCellTableViewController(parentResponder), ButtonRowDelegate(header, nullptr), m_numberOfColumns(0), m_numberOfColumnsNeedUpdate(true), + m_firstMemoizedColumn(INT_MAX), + m_firstMemoizedRow(INT_MAX), m_abscissaParameterController(this) { } @@ -124,16 +129,12 @@ void ValuesController::willDisplayCellAtLocation(HighlightCell * cell, int i, in willDisplayCellAtLocationWithDisplayMode(cell, i, j, Preferences::sharedPreferences()->displayMode()); // The cell is not a title cell and not editable if (typeAtLocation(i,j) == k_notEditableValueCellType) { - constexpr int bufferSize = 2*PrintFloat::charSizeForFloatsWithPrecision(Preferences::LargeNumberOfSignificantDigits)+3; - char buffer[bufferSize]; // The largest buffer holds (-1.234567E-123;-1.234567E-123) // Special case: last row if (j == numberOfElementsInColumn(i) + 1) { - buffer[0] = 0; + static_cast(cell)->setText(""); } else { - double x = intervalAtColumn(i)->element(j-1); - printEvaluationOfAbscissaAtColumn(x, i, buffer, bufferSize); + static_cast(cell)->setText(memoizedBufferForCell(i, j)); } - static_cast(cell)->setText(buffer); } } @@ -188,6 +189,7 @@ Responder * ValuesController::defaultController() { void ValuesController::viewWillAppear() { EditableCellTableViewController::viewWillAppear(); header()->setSelectedButton(-1); + resetMemoization(); } void ValuesController::viewDidDisappear() { @@ -233,4 +235,64 @@ FunctionStore * ValuesController::functionStore() const { return FunctionApp::app()->functionStore(); } +// Function evaluation memoization + +void ValuesController::resetMemoization() { + m_firstMemoizedColumn = INT_MAX; + m_firstMemoizedRow = INT_MAX; +} + +void ValuesController::moveMemoizedBuffer(int destinationI, int destinationJ, int sourceI, int sourceJ) { + strlcpy(memoizedBufferAtIndex(destinationJ*numberOfMemoizedColumn() + destinationI), memoizedBufferAtIndex(sourceJ*numberOfMemoizedColumn() + sourceI), k_valuesCellBufferSize); +} + +char * ValuesController::memoizedBufferForCell(int i, int j) { + // Conversion of coordinates from absolute table to values table + int valuesI = valuesColumnForAbsoluteColumn(i); + int valuesJ = valuesRowForAbsoluteRow(j); + int nbOfMemoizedColumns = numberOfMemoizedColumn(); + /* Compute the required offset to apply to the memoized table in order to + * display cell (i,j) */ + int offsetI = 0; + int offsetJ = 0; + if (valuesI < m_firstMemoizedColumn) { + offsetI = valuesI - m_firstMemoizedColumn; + } else if (valuesI >= m_firstMemoizedColumn + nbOfMemoizedColumns) { + offsetI = valuesI - nbOfMemoizedColumns - m_firstMemoizedColumn + 1; + } + if (valuesJ < m_firstMemoizedRow) { + offsetJ = valuesJ - m_firstMemoizedRow; + } else if (valuesJ >= m_firstMemoizedRow + k_maxNumberOfRows) { + offsetJ = valuesJ - k_maxNumberOfRows - m_firstMemoizedRow + 1; + } + + // Apply the offset + if (offsetI != 0 || offsetJ != 0) { + m_firstMemoizedColumn = m_firstMemoizedColumn + offsetI; + m_firstMemoizedRow = m_firstMemoizedRow + offsetJ; + // Translate already memoized cells + int maxI = numberOfValuesColumns(); + for (int ii = offsetI > 0 ? 0 : minInt(nbOfMemoizedColumns, maxI)-1; offsetI > 0 ? ii < minInt(-offsetI + nbOfMemoizedColumns, maxI) : ii >= -offsetI; ii += offsetI > 0 ? 1 : -1) { + int maxJ = numberOfElementsInColumn(absoluteColumnForValuesColumn(ii+m_firstMemoizedColumn)); + for (int jj = offsetJ > 0 ? 0 : minInt(k_maxNumberOfRows, maxJ)-1; offsetJ > 0 ? jj < minInt(-offsetJ+k_maxNumberOfRows, maxJ) : jj >= -offsetJ; jj += offsetJ > 0 ? 1 : -1) { + moveMemoizedBuffer(ii, jj, ii+offsetI, jj+offsetJ); + } + } + // Compute the buffer of the new cells of the memoized table + for (int ii = 0; ii < minInt(nbOfMemoizedColumns, maxI); ii++) { + int maxJ = numberOfElementsInColumn(absoluteColumnForValuesColumn(ii+m_firstMemoizedColumn)); + for (int jj = 0; jj < minInt(k_maxNumberOfRows, maxJ); jj++) { + // Escape if already filled + if (ii >= -offsetI && ii < -offsetI + nbOfMemoizedColumns && jj >= -offsetJ && jj < -offsetJ + k_maxNumberOfRows) { + continue; + } + fillMemoizedBuffer(absoluteColumnForValuesColumn(m_firstMemoizedColumn + ii), + absoluteRowForValuesRow(m_firstMemoizedRow + jj), + jj * numberOfMemoizedColumn() + ii); + } + } + } + return memoizedBufferAtIndex((valuesJ-m_firstMemoizedRow)*numberOfMemoizedColumn() + (valuesI-m_firstMemoizedColumn)); +} + } diff --git a/apps/shared/values_controller.h b/apps/shared/values_controller.h index ad691e669..470c32e9b 100644 --- a/apps/shared/values_controller.h +++ b/apps/shared/values_controller.h @@ -50,12 +50,47 @@ protected: int numberOfElementsInColumn(int columnIndex) const override; mutable int m_numberOfColumns; mutable bool m_numberOfColumnsNeedUpdate; + + /* Function evaluation memoization + * We memoize value cell buffers in order to increase scrolling speed. However, + * abscissa cells are not memoized (their computation does not require any + * expression evaluation and is therefore not significantly long). + * In the following, we refer to 3 different tables: + * - the absolute table - the complete displayed table + * - the table of values cells only (the absolute table from which we pruned + * the titles and the abscissa columns) + * - the memoized table (which is a subset of the table of values cells) + */ + static constexpr int k_valuesCellBufferSize = 2*Poincare::PrintFloat::charSizeForFloatsWithPrecision(Poincare::Preferences::LargeNumberOfSignificantDigits)+3; // The largest buffer holds (-1.234567E-123;-1.234567E-123) + void resetMemoization(); + virtual char * memoizedBufferAtIndex(int i) = 0; + virtual int numberOfMemoizedColumn() = 0; private: virtual void setStartEndMessages(Shared::IntervalParameterController * controller, int column) = 0; Responder * tabController() const override; bool cellAtLocationIsEditable(int columnIndex, int rowIndex) override; double dataAtLocation(int columnIndex, int rowIndex) override; - virtual void printEvaluationOfAbscissaAtColumn(double abscissa, int columnIndex, char * buffer, const int bufferSize) = 0; + virtual int numberOfValuesColumns() { return functionStore()->numberOfActiveFunctions(); } + + /* Function evaluation memoization + * The following 4 methods convert coordinate from the absolute table to the + * table of values cell only and vice-versa. */ + virtual int valuesColumnForAbsoluteColumn(int column) { return column - 1; } // Subtract the abscissa column + int valuesRowForAbsoluteRow(int row) { return row - 1; } // Subtract the title row + virtual int absoluteColumnForValuesColumn(int column) { return column + 1; } // Add the abscissa column + int absoluteRowForValuesRow(int row) { return row + 1; } // Add the title row + // Coordinates of memoizedBufferForCell refer to the absolute table + char * memoizedBufferForCell(int i, int j); + // Coordinates of moveMemoizedBuffer refer to the memoized table + void moveMemoizedBuffer(int destinationI, int destinationJ, int sourceI, int sourceJ); + // Coordinates of fillMemoizedBuffer refer to the absolute table but the index + // refers to the memoized table + virtual void fillMemoizedBuffer(int i, int j, int index) = 0; + /* m_firstMemoizedColumn and m_firstMemoizedRow are coordinates of the table + * of values cells.*/ + mutable int m_firstMemoizedColumn; + mutable int m_firstMemoizedRow; + virtual Interval * intervalAtColumn(int columnIndex) = 0; virtual I18n::Message valuesParameterMessageAtColumn(int columnIndex) const = 0; int maxNumberOfElements() const override {