Permalink
Browse files

Use boost::flyweight to intern the wxString members of AssDialogue

100 no-op non-amend commits on a subtitle file with 6689 dialogue lines,
with the undo limit set to 100:

Without flyweight:
	No video open:
		Initial memory usage: 30.6 MB
		Final memory usage: 498.0 MB
		Elapsed time: 6.3 seconds
	Video open, using libass:
		Initial memory usage: 54.3 MB
		Final memory usage: 653.3 MB
		Elapsed time: 23.7 seconds

With flyweight:
	No video open:
		Initial memory usage: 26.0 MB
		Final memory usage: 104.5 MB
		Elapsed time: 3.0 seconds
	Video open, using libass:
		Initial memory usage: 46.7 MB
		Final memory usage: 251.8 MB
		Elapsed time: 13.0 seconds

No video open:
	Memory usage: -79%
	Time: -52%
Video open:
	Memory usage: -61.5%
	Time: -45%

100 no-op amend commits on a line in the middle of a subtitle file with
6689 dialogue lines, with video open:

Without flyweight:
	Initial memory usage: 48.2 MB
	Final memory usage: 182.3 MB
	Elapsed time: 22.3 seconds

With flyweight:
	Initial memory usage: 39.8 MB
	Final memory usage: 165.8 MB
	Elapsed time: 13.8 seconds

Note: The large jump in memory usage here is due to that the benchmark
is blocking the main thread, so at the end there are ~100 video frames
waiting to be displayed.
  • Loading branch information...
1 parent c7c270c commit d5aae26d838db103fe84f6b4c3ae72ca65403019 @tgoyne tgoyne committed Dec 4, 2012
View
@@ -69,6 +69,7 @@
#include <sys/param.h>
#endif
+#include <boost/flyweight.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/indirected.hpp>
@@ -33,6 +33,7 @@
#include "config.h"
+#include <boost/algorithm/string/join.hpp>
#include <fstream>
#include <list>
@@ -47,6 +48,10 @@
#include <libaegisub/of_type_adaptor.h>
+std::size_t hash_value(wxString const& s) {
+ return wxStringHash()(s);
+}
+
AssDialogue::AssDialogue()
: AssEntry(wxString())
, Comment(false)
@@ -128,15 +133,11 @@ bool AssDialogue::Parse(wxString const& rawData) {
// Get style
if (!tkn.HasMoreTokens()) return false;
- Style = tkn.GetNextToken();
- Style.Trim(true);
- Style.Trim(false);
+ Style = tkn.GetNextToken().Trim(true).Trim(false);
// Get actor
if (!tkn.HasMoreTokens()) return false;
- Actor = tkn.GetNextToken();
- Actor.Trim(true);
- Actor.Trim(false);
+ Actor = tkn.GetNextToken().Trim(true).Trim(false);
// Get margins
for (int i = 0; i < 3; ++i) {
@@ -145,9 +146,7 @@ bool AssDialogue::Parse(wxString const& rawData) {
}
if (!tkn.HasMoreTokens()) return false;
- Effect = tkn.GetNextToken();
- Effect.Trim(true);
- Effect.Trim(false);
+ Effect = tkn.GetNextToken().Trim(true).Trim(false);
// Get text
Text = rawData.Mid(pos + tkn.GetPosition());
@@ -172,7 +171,7 @@ wxString AssDialogue::GetData(bool ssa) const {
s, a,
Margin[0], Margin[1], Margin[2],
e,
- Text);
+ Text.get());
// Make sure that final has no line breaks
str.Replace("\n", "");
@@ -193,17 +192,17 @@ std::auto_ptr<boost::ptr_vector<AssDialogueBlock>> AssDialogue::ParseTags() cons
boost::ptr_vector<AssDialogueBlock> Blocks;
// Empty line, make an empty block
- if (Text.empty()) {
+ if (Text.get().empty()) {
Blocks.push_back(new AssDialogueBlockPlain);
return Blocks.release();
}
int drawingLevel = 0;
- for (size_t len = Text.size(), cur = 0; cur < len; ) {
+ for (size_t len = Text.get().size(), cur = 0; cur < len; ) {
// Overrides block
- if (Text[cur] == '{') {
- size_t end = Text.find('}', cur);
+ if (Text.get()[cur] == '{') {
+ size_t end = Text.get().find('}', cur);
// VSFilter requires that override blocks be closed, while libass
// does not. We match VSFilter here.
@@ -212,7 +211,7 @@ std::auto_ptr<boost::ptr_vector<AssDialogueBlock>> AssDialogue::ParseTags() cons
++cur;
// Get contents of block
- wxString work = Text.substr(cur, end - cur);
+ wxString work = Text.get().substr(cur, end - cur);
cur = end + 1;
if (work.size() && work.find('\\') == wxString::npos) {
@@ -240,13 +239,13 @@ std::auto_ptr<boost::ptr_vector<AssDialogueBlock>> AssDialogue::ParseTags() cons
// Plain-text/drawing block
plain:
wxString work;
- size_t end = Text.find('{', cur + 1);
+ size_t end = Text.get().find('{', cur + 1);
if (end == wxString::npos) {
- work = Text.substr(cur);
+ work = Text.get().substr(cur);
cur = len;
}
else {
- work = Text.substr(cur, end - cur);
+ work = Text.get().substr(cur, end - cur);
cur = end;
}
@@ -265,12 +264,12 @@ void AssDialogue::StripTags() {
void AssDialogue::StripTag(wxString const& tag_name) {
boost::ptr_vector<AssDialogueBlock> blocks(ParseTags());
- Text.clear();
+ wxString new_text;
// Look for blocks
for (auto& block : blocks) {
if (block.GetType() != BLOCK_OVERRIDE) {
- Text += block.GetText();
+ new_text += block.GetText();
continue;
}
@@ -282,15 +281,16 @@ void AssDialogue::StripTag(wxString const& tag_name) {
}
if (!temp.empty())
- Text += "{" + temp + "}";
+ new_text += "{" + temp + "}";
}
+
+ Text = new_text;
}
+static wxString get_text(AssDialogueBlock &d) { return d.GetText(); }
void AssDialogue::UpdateText(boost::ptr_vector<AssDialogueBlock>& blocks) {
if (blocks.empty()) return;
- Text.clear();
- for (auto& block : blocks)
- Text += block.GetText();
+ Text = join(blocks | boost::adaptors::transformed(get_text), wxS(""));
}
void AssDialogue::SetMarginString(wxString const& origvalue, int which) {
View
@@ -37,6 +37,7 @@
#include <libaegisub/exception.h>
+#include <boost/flyweight.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <vector>
@@ -50,6 +51,8 @@ enum AssBlockType {
class AssOverrideParameter;
class AssOverrideTag;
+std::size_t hash_value(wxString const& s);
+
/// @class AssDialogueBlock
/// @brief AssDialogue Blocks
///
@@ -131,13 +134,13 @@ class AssDialogue : public AssEntry {
/// Ending time
AssTime End;
/// Style name
- wxString Style;
+ boost::flyweight<wxString> Style;
/// Actor name
- wxString Actor;
+ boost::flyweight<wxString> Actor;
/// Effect name
- wxString Effect;
+ boost::flyweight<wxString> Effect;
/// Raw text data
- wxString Text;
+ boost::flyweight<wxString> Text;
AssEntryGroup Group() const override { return ENTRY_DIALOGUE; }
View
@@ -607,15 +607,15 @@ void BaseGrid::GetRowStrings(int row, AssDialogue *line, bool *paint_columns, wx
// Hidden overrides
if (replace) {
- strings[10].reserve(line->Text.size());
+ strings[10].reserve(line->Text.get().size());
size_t start = 0, pos;
- while ((pos = line->Text.find('{', start)) != wxString::npos) {
- strings[10] += line->Text.Mid(start, pos - start);
+ while ((pos = line->Text.get().find('{', start)) != wxString::npos) {
+ strings[10] += line->Text.get().Mid(start, pos - start);
strings[10] += rep_char;
- start = line->Text.find('}', pos);
+ start = line->Text.get().find('}', pos);
if (start != wxString::npos) ++start;
}
- strings[10] += line->Text.Mid(start);
+ strings[10] += line->Text.get().Mid(start);
}
// Show overrides
@@ -207,7 +207,7 @@ void set_tag(AssDialogue *line, boost::ptr_vector<AssDialogueBlock> &blocks, wxS
// Cursor is in a comment block, so try the previous block instead
if (plain->GetText().StartsWith("{")) {
--blockn;
- start = line->Text.rfind('{', start);
+ start = line->Text.get().rfind('{', start);
}
else
break;
@@ -227,7 +227,7 @@ void set_tag(AssDialogue *line, boost::ptr_vector<AssDialogueBlock> &blocks, wxS
wxString insert = tag + value;
int shift = insert.size();
if (plain || blockn < 0) {
- line->Text = line->Text.Left(start) + "{" + insert + "}" + line->Text.Mid(start);
+ line->Text = line->Text.get().Left(start) + "{" + insert + "}" + line->Text.get().Mid(start);
shift += 2;
blocks = line->ParseTags();
}
@@ -681,11 +681,11 @@ static void combine_lines(agi::Context *c, void (*combiner)(AssDialogue *, AssDi
}
static void combine_karaoke(AssDialogue *first, AssDialogue *second) {
- first->Text += wxString::Format("{\\k%d}%s", (second->Start - first->End) / 10, second->Text);
+ first->Text = wxString::Format("%s{\\k%d}%s", first->Text.get(), (second->Start - first->End) / 10, second->Text.get());
}
static void combine_concat(AssDialogue *first, AssDialogue *second) {
- first->Text += " " + second->Text;
+ first->Text = first->Text + " " + second->Text;
}
static void combine_drop(AssDialogue *, AssDialogue *) { }
@@ -202,7 +202,7 @@ namespace {
void resample_line(resample_state *state, AssEntry &line) {
AssDialogue *diag = dynamic_cast<AssDialogue*>(&line);
- if (diag && !(diag->Comment && (diag->Effect.StartsWith("template") || diag->Effect.StartsWith("code")))) {
+ if (diag && !(diag->Comment && (diag->Effect.get().StartsWith("template") || diag->Effect.get().StartsWith("code")))) {
boost::ptr_vector<AssDialogueBlock> blocks(diag->ParseTags());
for (auto block : blocks | agi::of_type<AssDialogueBlockOverride>())
@@ -243,6 +243,13 @@ void SearchReplaceEngine::FindNext() {
ReplaceNext(false);
}
+static boost::flyweight<wxString> *get_text(AssDialogue *cur, int field) {
+ if (field == 0) return &cur->Text;
+ else if (field == 1) return &cur->Style;
+ else if (field == 2) return &cur->Actor;
+ else if (field == 3) return &cur->Effect;
+ else throw wxString("Invalid field");
+}
void SearchReplaceEngine::ReplaceNext(bool DoReplace) {
if (!CanContinue) {
@@ -267,7 +274,6 @@ void SearchReplaceEngine::ReplaceNext(bool DoReplace) {
int start = curLine;
int nrows = context->subsGrid->GetRows();
bool found = false;
- wxString *Text = nullptr;
size_t tempPos;
int regFlags = wxRE_ADVANCED;
if (!matchCase) {
@@ -276,8 +282,9 @@ void SearchReplaceEngine::ReplaceNext(bool DoReplace) {
}
// Search for it
+ boost::flyweight<wxString> *Text = nullptr;
while (!found) {
- Text = GetText(context->subsGrid->GetDialogue(curLine), field);
+ Text = get_text(context->subsGrid->GetDialogue(curLine), field);
if (DoReplace && LastWasFind)
tempPos = pos;
else
@@ -287,7 +294,7 @@ void SearchReplaceEngine::ReplaceNext(bool DoReplace) {
if (isReg) {
wxRegEx regex (LookFor,regFlags);
if (regex.IsValid()) {
- if (regex.Matches(Text->Mid(tempPos))) {
+ if (regex.Matches(Text->get().Mid(tempPos))) {
size_t match_start;
regex.GetMatch(&match_start,&matchLen,0);
pos = match_start + tempPos;
@@ -298,7 +305,7 @@ void SearchReplaceEngine::ReplaceNext(bool DoReplace) {
// Normal
else {
- wxString src = Text->Mid(tempPos);
+ wxString src = Text->get().Mid(tempPos);
if (!matchCase) src.MakeLower();
int textPos = src.Find(LookFor);
if (textPos != -1) {
@@ -325,16 +332,16 @@ void SearchReplaceEngine::ReplaceNext(bool DoReplace) {
if (DoReplace) {
// Replace with regular expressions
if (isReg) {
- wxString toReplace = Text->Mid(pos,matchLen);
+ wxString toReplace = Text->get().Mid(pos,matchLen);
wxRegEx regex(LookFor,regFlags);
regex.ReplaceFirst(&toReplace,ReplaceWith);
- *Text = Text->Left(pos) + toReplace + Text->Mid(pos+matchLen);
+ *Text = Text->get().Left(pos) + toReplace + Text->get().Mid(pos+matchLen);
replaceLen = toReplace.Length();
}
// Normal replace
else {
- *Text = Text->Left(pos) + ReplaceWith + Text->Mid(pos+matchLen);
+ *Text = Text->get().Left(pos) + ReplaceWith + Text->get().Mid(pos+matchLen);
replaceLen = ReplaceWith.Length();
}
@@ -387,7 +394,7 @@ void SearchReplaceEngine::ReplaceAll() {
if (inSel && hasSelection && !sel.count(diag))
continue;
- wxString *Text = GetText(diag, field);
+ boost::flyweight<wxString> *Text = get_text(diag, field);
// Regular expressions
if (isReg) {
@@ -398,7 +405,9 @@ void SearchReplaceEngine::ReplaceAll() {
// A zero length match (such as '$') will always be replaced
// maxMatches times, which is almost certainly not what the user
// wanted, so limit it to one replacement in that situation
- count += reg.Replace(Text, ReplaceWith, len > 0 ? 1000 : 1);
+ wxString repl(*Text);
+ count += reg.Replace(&repl, ReplaceWith, len > 0 ? 1000 : 1);
+ *Text = repl;
}
}
// Normal replace
@@ -424,8 +433,10 @@ void SearchReplaceEngine::ReplaceAll() {
*Text = Left + Right;
}
}
- else if(Text->Contains(LookFor)) {
- count += Text->Replace(LookFor, ReplaceWith);
+ else if(Text->get().Contains(LookFor)) {
+ wxString repl(*Text);
+ count += repl.Replace(LookFor, ReplaceWith);
+ *Text = repl;
}
}
}
@@ -475,12 +486,4 @@ void SearchReplaceEngine::OpenDialog (bool replace) {
hasReplace = replace;
}
-wxString *SearchReplaceEngine::GetText(AssDialogue *cur, int field) {
- if (field == 0) return &cur->Text;
- else if (field == 1) return &cur->Style;
- else if (field == 2) return &cur->Actor;
- else if (field == 3) return &cur->Effect;
- else throw wxString("Invalid field");
-}
-
SearchReplaceEngine Search;
@@ -59,8 +59,6 @@ class SearchReplaceEngine {
wxString LookFor;
wxString ReplaceWith;
- wxString *GetText(AssDialogue *cur, int field);
-
public:
agi::Context *context;
@@ -67,7 +67,7 @@ enum {
DEFINE_SIMPLE_EXCEPTION(BadRegex, agi::InvalidInputException, "bad_regex")
-static wxString AssDialogue::* get_field(int field_n) {
+static boost::flyweight<wxString> AssDialogue::* get_field(int field_n) {
switch(field_n) {
case FIELD_TEXT: return &AssDialogue::Text; break;
case FIELD_STYLE: return &AssDialogue::Style; break;
@@ -109,7 +109,7 @@ static std::set<AssDialogue*> process(wxString match_text, bool match_case, int
match_case = false;
}
- wxString AssDialogue::*field = get_field(field_n);
+ boost::flyweight<wxString> AssDialogue::*field = get_field(field_n);
std::function<bool (wxString)> pred = get_predicate(mode, &re, match_case, match_text);
std::set<AssDialogue*> matches;
Oops, something went wrong.

0 comments on commit d5aae26

Please sign in to comment.