Beachten
Siehe auch diese Antwort: https://stackoverflow.com/a/21708215, die hier unten die Basis für EDIT 2 war .
Ich habe die Schleife auf 1000000 erweitert, um ein besseres Timing-Maß zu erhalten.
Dies ist mein Python-Timing:
real 0m2.038s
user 0m2.009s
sys 0m0.024s
Hier ist ein Äquivalent Ihres Codes, nur ein bisschen hübscher:
#include <regex>
#include <vector>
#include <string>
std::vector<std::string> split(const std::string &s, const std::regex &r)
{
return {
std::sregex_token_iterator(s.begin(), s.end(), r, -1),
std::sregex_token_iterator()
};
}
int main()
{
const std::regex r(" +");
for(auto i=0; i < 1000000; ++i)
split("a b c", r);
return 0;
}
Zeitliche Koordinierung:
real 0m5.786s
user 0m5.779s
sys 0m0.005s
Dies ist eine Optimierung, um die Konstruktion / Zuordnung von Vektor- und Zeichenfolgenobjekten zu vermeiden:
#include <regex>
#include <vector>
#include <string>
void split(const std::string &s, const std::regex &r, std::vector<std::string> &v)
{
auto rit = std::sregex_token_iterator(s.begin(), s.end(), r, -1);
auto rend = std::sregex_token_iterator();
v.clear();
while(rit != rend)
{
v.push_back(*rit);
++rit;
}
}
int main()
{
const std::regex r(" +");
std::vector<std::string> v;
for(auto i=0; i < 1000000; ++i)
split("a b c", r, v);
return 0;
}
Zeitliche Koordinierung:
real 0m3.034s
user 0m3.029s
sys 0m0.004s
Dies entspricht einer Leistungsverbesserung von nahezu 100%.
Der Vektor wird vor der Schleife erstellt und kann seinen Speicher in der ersten Iteration erweitern. Danach erfolgt keine Speicherfreigabe durch clear()
, der Vektor verwaltet den Speicher und erstellt Zeichenfolgen an Ort und Stelle .
Eine weitere Leistungssteigerung wäre die vollständige Vermeidung von Konstruktion / Zerstörung std::string
und damit die Zuweisung / Freigabe der Objekte.
Dies ist ein Vorbehalt in diese Richtung:
#include <regex>
#include <vector>
#include <string>
void split(const char *s, const std::regex &r, std::vector<std::string> &v)
{
auto rit = std::cregex_token_iterator(s, s + std::strlen(s), r, -1);
auto rend = std::cregex_token_iterator();
v.clear();
while(rit != rend)
{
v.push_back(*rit);
++rit;
}
}
Zeitliche Koordinierung:
real 0m2.509s
user 0m2.503s
sys 0m0.004s
Eine ultimative Verbesserung wäre eine Rückgabe std::vector
von const char *
as, bei der jeder Zeichenzeiger auf eine Teilzeichenfolge in der ursprünglichen s
c-Zeichenfolge selbst verweist . Das Problem ist, dass Sie dies nicht tun können, da jeder von ihnen nicht nullterminiert wäre (siehe dazu die Verwendung von C ++ 1y string_ref
in einem späteren Beispiel).
Diese letzte Verbesserung könnte auch damit erreicht werden:
#include <regex>
#include <vector>
#include <string>
void split(const std::string &s, const std::regex &r, std::vector<std::string> &v)
{
auto rit = std::cregex_token_iterator(s.data(), s.data() + s.length(), r, -1);
auto rend = std::cregex_token_iterator();
v.clear();
while(rit != rend)
{
v.push_back(*rit);
++rit;
}
}
int main()
{
const std::regex r(" +");
std::vector<std::string> v;
for(auto i=0; i < 1000000; ++i)
split("a b c", r, v);
return 0;
}
Ich habe die Samples mit Clang 3.3 (aus dem Trunk) mit -O3 erstellt. Möglicherweise können andere Regex-Bibliotheken eine bessere Leistung erbringen, aber in jedem Fall sind Zuweisungen / Freigaben häufig ein Leistungseinbruch.
Boost.Regex
Dies ist das boost::regex
Timing für das Beispiel der c-String- Argumente:
real 0m1.284s
user 0m1.278s
sys 0m0.005s
Der gleiche Code boost::regex
und die gleiche std::regex
Schnittstelle in diesem Beispiel sind identisch und müssen nur den Namespace und das Include ändern.
Die besten Wünsche, damit es mit der Zeit besser wird, C ++ stdlib Regex-Implementierungen stecken noch in den Kinderschuhen.
BEARBEITEN
Der Vollständigkeit halber habe ich dies versucht (der oben erwähnte Vorschlag zur "ultimativen Verbesserung") und es hat die Leistung der entsprechenden std::vector<std::string> &v
Version in nichts verbessert :
#include <regex>
#include <vector>
#include <string>
template<typename Iterator> class intrusive_substring
{
private:
Iterator begin_, end_;
public:
intrusive_substring(Iterator begin, Iterator end) : begin_(begin), end_(end) {}
Iterator begin() {return begin_;}
Iterator end() {return end_;}
};
using intrusive_char_substring = intrusive_substring<const char *>;
void split(const std::string &s, const std::regex &r, std::vector<intrusive_char_substring> &v)
{
auto rit = std::cregex_token_iterator(s.data(), s.data() + s.length(), r, -1);
auto rend = std::cregex_token_iterator();
v.clear();
while(rit != rend)
{
v.emplace_back(rit->first, rit->second);
++rit;
}
}
int main()
{
const std::regex r(" +");
std::vector<intrusive_char_substring> v;
for(auto i=0; i < 1000000; ++i)
split("a b c", r, v);
return 0;
}
Dies hat mit dem Vorschlag array_ref und string_ref zu tun . Hier ist ein Beispielcode, der ihn verwendet:
#include <regex>
#include <vector>
#include <string>
#include <string_ref>
void split(const std::string &s, const std::regex &r, std::vector<std::string_ref> &v)
{
auto rit = std::cregex_token_iterator(s.data(), s.data() + s.length(), r, -1);
auto rend = std::cregex_token_iterator();
v.clear();
while(rit != rend)
{
v.emplace_back(rit->first, rit->length());
++rit;
}
}
int main()
{
const std::regex r(" +");
std::vector<std::string_ref> v;
for(auto i=0; i < 1000000; ++i)
split("a b c", r, v);
return 0;
}
Es ist auch billiger, einen Vektor von string_ref
anstelle von string
Kopien für den Fall der split
Vektorrückgabe zurückzugeben.
BEARBEITEN 2
Diese neue Lösung kann per Ausgabe ausgegeben werden. Ich habe die string_view
( string_ref
umbenannte) libc ++ - Implementierung von Marshall Clow verwendet, die unter https://github.com/mclow/string_view zu finden ist .
#include <string>
#include <string_view>
#include <boost/regex.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/iterator/transform_iterator.hpp>
using namespace std;
using namespace std::experimental;
using namespace boost;
string_view stringfier(const cregex_token_iterator::value_type &match) {
return {match.first, static_cast<size_t>(match.length())};
}
using string_view_iterator =
transform_iterator<decltype(&stringfier), cregex_token_iterator>;
iterator_range<string_view_iterator> split(string_view s, const regex &r) {
return {
string_view_iterator(
cregex_token_iterator(s.begin(), s.end(), r, -1),
stringfier
),
string_view_iterator()
};
}
int main() {
const regex r(" +");
for (size_t i = 0; i < 1000000; ++i) {
split("a b c", r);
}
}
Zeitliche Koordinierung:
real 0m0.385s
user 0m0.385s
sys 0m0.000s
Beachten Sie, wie schnell dies im Vergleich zu früheren Ergebnissen ist. Natürlich füllt es kein vector
innerhalb der Schleife (und passt wahrscheinlich auch nicht zu etwas im Voraus), aber Sie erhalten trotzdem einen Bereich, den Sie mit bereichsbasiert übergreifen for
oder sogar zum Füllen eines Bereichs verwenden können vector
.
Da sich das iterator_range
Erstellen string_view
über ein Original string
(oder eine nullterminierte Zeichenfolge ) erstreckt, wird dies sehr leicht und generiert niemals unnötige Zeichenfolgenzuweisungen.
Nur um die Verwendung dieser split
Implementierung zu vergleichen, aber tatsächlich eine zu füllen vector
, könnten wir dies tun:
int main() {
const regex r(" +");
vector<string_view> v;
v.reserve(10);
for (size_t i = 0; i < 1000000; ++i) {
copy(split("a b c", r), back_inserter(v));
v.clear();
}
}
Dies verwendet einen Boost-Range-Kopieralgorithmus, um den Vektor in jeder Iteration zu füllen. Das Timing ist:
real 0m1.002s
user 0m0.997s
sys 0m0.004s
Wie zu sehen ist, kein großer Unterschied im Vergleich zur optimierten string_view
Ausgabeparameterversion.
Beachten Sie auch, dass es einen Vorschlag für einen gibtstd::split
, der so funktionieren würde.