Подскажите, чем измерить скорость выборки селекторами jQuery? Раньше для FF был java.console,
но, к сожалению, он уже не обновляется, а на новых версиях лисы не пашет.
Нужен именно плагин, а не какой-либо он-лайн сервис.
Решение: Чтобы измерить скорость выборки, пишем в JS коде:
console.time("имя");
выборка с помощью 1 или нескольких селекторов
console.timeEnd("то же имя");
И в консоли Firebug будет писаться время выборки, если между time и timeEnd была
только выборка. Между запуском и остановкой таймера можно запихнуть что угодно, и будет
измеряться, соответственно, общее время выполнения этой части скрипта.
P.S. В DragonFly оперы в консоли также все работает.
Ответы
Ответ 1
FireBug думаю поможет
Ответ 2
Самый не удобный и бесполезный способ. В файрфоксе результаты будут отличаться незначительно,
куда интересней измерить скорость различных селекторов в других браузерах, таких как
ие, там выигрыш во времени от оптимизации селекторов будет просто громадным, Можете
посмотреть скорость различных селекторов у всех популярных js-библиотек в тесте slicktest.
Если общей информации не достаточно, можете скачать его и запустить локально, заменив
предложенные селекторы на ваши и тестировать именно на вашем документе. Можно сразу
тестировать везде, а не только в удобном файрбаге.
Ответ 3
Скорость отработки того или иного селектора в значительной мере будет зависеть от
куска DOM на котором его применить.
Чтобы грамотно использовать селекторы нужно понять, как работает движок sizzle, который
и занимается их анализом и парсингом DOM.
Очень хорошо об этом написано тут: http://habrahabr.ru/blogs/jquery/111195/
Здравствуйте! Сейчас пытаюсь искать алгоритмы Маркова с помощью генетических алгоритмов.
Есть такой вот код:
#include
#include
#include
#include
#include
#include
#include
using std::cin;
using std::cout;
using std::endl;
using std::sort;
using std::pair;
using std::vector;
using std::string;
typedef pair rule;
typedef vector algorithm;
typedef pair test;
void print_rule(rule r)
{
cout< "< prevs;
int step = 0;
bool loop=false;
bool found = false;
for(int i=0; i100)
break;
for(int i=0; i '"<0)
{
str.erase(rand()%str.length(), 1);
return str;
}
else if(chance<75 && str.length()>0)
{
auto index = str.begin()+rand()%str.length();
str.insert(index, 1, (char)(rand()%94+32));
return str;
}
return str;
}
rule mutate_rule(rule r)
{
string left = r.first;
string right = r.second;
int chance = rand()%100;
if(chance<33)
{
left = mutate_string(left);
}
else if(chance<66)
{
right = mutate_string(right);
}
else
{
left = mutate_string(left);
// cout< 1)
{
auto index = algo.begin()+rand()%algo.size();
algo.erase(index);
return algo;
}
else
{
int index = rand()%algo.size();
rule new_rule = mutate_rule(algo[index]);
algo[index] = new_rule;
return algo;
}
}
algorithm random_algoritm()
{
int length = rand()%10+1;
algorithm ret;
for(int i=0; i& tests)
{
int length = 0;
for(int i=0; i& tests)
{
int ret = 0;
for(int i=0; i& tests)
{
int ret = 0;
for(int i=0; i& tests)
{
const int goal = max_fitness(tests);
int best = -10000;
vector population;
for(int i=0; i<100; i++)
population.push_back(random_algoritm());
int generation = 0;
int last_print = 0;
int start = time(NULL);
pair pop[100];
while(best != goal)
{
generation++;
for(int i=0; i<100; i++)
pop[i]=make_pair(population[i], fitness(population[i], tests));
sort(pop, pop+100,
[](const pair& a, const pair& b)
{return a.second > b.second;});
population[0] = pop[0].first;
for(int i=1; i<100; i++)
{
int chance=rand()%100;
if(chance>i)
{
population[i] = mutate_algoritm(pop[0].first);
}
else
population[i] = pop[0].first;
}
const int first = fitness(population[0], tests);
if(first>best)
{
best=first;
}
}
return population[0];
}
algorithm optimize_algorithm(const vector tests, algorithm algo)
{
vector population;
for(int i=0; i<100; i++)
population.push_back(algo);
int generation = 0;
int last_print = 0;
int best = -algo.size();
pair pop[100];
while(generation<10000)
{
generation++;
for(int i=0; i<100; i++)
pop[i]=make_pair(population[i], fitness_opt(population[i], tests));
sort(pop, pop+100,
[](const pair& a, const pair& b)
{return a.second > b.second;});
population[0] = pop[0].first;
for(int i=1; i<100; i++)
{
int chance=rand()%100;
if(chance>i)
{
population[i] = mutate_algoritm(pop[0].first);
}
else
population[i] = pop[0].first;
}
const int first = fitness_opt(population[0], tests);
if(first>best)
best=first;
}
return population[0];
}
int main()
{
// const vector tests = {{"||*||", "||||"},
// {"||*|||", "||||||"},
// {"||||*||", "||||||||"},
// {"||||*||||", "||||||||||||||||"}};
// const vector tests = {{"||+||", "||||"},
// {"|+||", "|||"},
// {"|||+|", "||||"}};
// const vector tests = {{"||-||", ""},
// {"|||-|", "||"},
// {"||-|", "|"}};
const vector tests = {{"||+||", "||||"},
{"|||+|", "||||"},
{"|+|", "||"}};
int start = time(NULL);
algorithm un_add = find_algorithm(tests);
cout<<"Found in "<
Ответы
Ответ 1
Навскидку предлагаю такую оптимизацию:
Код вида
for(int i=0; i<100; i++)
pop[i]=make_pair(population[i], fitness(population[i], tests));
sort(pop, pop+100,
[](const pair& a, const pair& b)
{return a.second > b.second;});
population[0] = pop[0].first;
...
заменить на:
pair pop = make_pair(&population[0], fitness(population[0], tests));
for(int i=1; i<100; i++)
{
int ft = fitness(population[i], tests);
if(ft > pop.second)
pop = make_pair(&population[i], ft);
}
algorithm popalg = *pop.first;
population[0] = popalg;
...
Ибо, как я понял по коду, вам надо найти алгоритм с максимальным соответствием тестам.
Для этого достаточно простого поиска максимума, а полноценная сортировка ни к чему.
Кроме того я заменил вектора указателями на них.
А вообще в таких случаях желательно пользоваться профилировщиком. Ибо бывает сложно
без измерений оценить, какую выгоду даст то или иное изменение кода.
Здравствуйте! Хотел спросить у разработчиков, у которых возможно был такой опыт!
В общем есть идея: писать код модульным образом (каждый модуль в своем файле), и инклудить
требуемые модули в итоге в главный файл (к примеру из всех 30 модулей нам нужно только
некоторые 5), или такой метод: в конце написания модулей, "склеить" их всех в один
файл и сделать один инклуд в главный файл? Для более ясной картины: инклудить 5 требуемых
файлов по 100 кб, и с ними работать, или инклудить 1 файл 3000 кб, со всеми сразу склеенными
модулями - что быстрее?
Ответы
Ответ 1
Вы думаете про оптимизацию на include ? так вы полный путь указывайте и никакого
поиска не будет. А вообще в php узкие места не при загрузке файлов обычно, а в первую
очередь в логике приложения, во вторую(а часто и в первую) - при обмене с базой данных
и в третью - при интерпретации кода, независимо от того в одном это файле или в нескольких,
тем более как вы сами заметили не каждый модуль понадобится при каждом запросе, т.е.
при использовании схемы "один большой файл", вы будете как минимум производить синтаксическую
проверку этого большого файла при каждом запуске.
Ответ 2
Вообще то такой трюк дает прирост производительности (в особенности в связке с акселератором).
require/include в любом случае тратит время на подключение файла (а само время конечно
же зависит от HDD и файловой системы), а SSD диски есть не у всех. Например возьмем
Zend и посмотрим тут, так что в этом есть смысл. Но, не любая архитектура проекта позволяет
реализовать подобное.
Ответ 3
Это бред. По сути вы дадите нагрузку больше.
';
echo memory_get_usage() . "\n"; //50632
?>
';
echo memory_get_usage() . "\n"; //50192
?>
вы можете в магазине сложить всё в один пакет, а можете рассортировать по пакетам,
нагрузка только увеличится (время на раскладку + вес пакетов)
другое дело если вы будете из магазина носить небольшие пакеты, т.е. отдельные файлы
рега.пхп лк.пхп, но защитить большой пакет легче, чем маленький, смысл у этой функции
только в подключении однотипных файлов например конфиг.пхп
а лучше не мучайтесь этой фигней, пишите как удобнее вам, намного больше уходит ввод
новых переменных
Ответ 4
Посмотрите мою статью с исчерпывающим исследованием по теме вопроса.
Сравнение производительности автозагрузки и объединения классов в один файл
Пытаюсь выбрать виртуальную машину, и не могу понять как их сравнивать. Когда читаю
описание любой ВМ - вижу много слов про используемые технологии, и почему они делают
эту ВМ самой быстрой. Но ведь не могут все они быть самыми быстрыми, какие-то из них
же должны быть быстрее других?
Как сравнивать разные виртуальные машины?
Ответы
Ответ 1
1. Наиболее быстрыми являются виртуальные машины контейнерного типа - все контейнеры
используют общее ядро и общие модули уровня ядра, но служебные структуры каждого процесса
инициализированы особым образом, что создает иллюзию совершенно разных серверов.
Примеры контейнеров - OpenVZ, Docker. Работают только на линуксе, потому что над
другими ОС невозможно надругаться настолько же сильно.
2. Следом за контейнерами в порядке уменьшения производительности идет паравиртуализация.
Паравиртуализация - это запуск гостевой ОС полностью на уровне пользователя, для этого
для нужной ОС готовят специальные драйвера, которые подменяют все те модули ядра, которым
ранее требовался доступ к привилегиям уровня ядра (ring 0 на архитектуре x86/64).
Пример паравиртуальных машин - XEN, User mode linux.
Ограничение - гостевая ОС для работы в паравиртуальном режиме должна быть совместима
с используемым гипервизором. Зачастую это ограничение можно читать как "гостевая ОС
должна быть линуксом", хотя есть и некоторые исключения.
3. Следующие по тяжести реализации - гипервизоры, использующие аппаратную виртуализацию.
Это опять XEN, KVM, VirtualBox, WMWare, Hyper-V и QEMU (в режиме KQEMU)...
Здесь уже нет никаких ограничений на гостевую ОС, кроме наличия под нее драйверов
для виртуальных устройств. Но многие реализации эмулируют в качестве "виртуальных"
реально существующие устройства, что упрощает проблему с драйверами.
Но тут надо понимать, что несмотря на возможность полной виртуализации на каждой
из этих ВМ, все они могут использовать частичную паравиртуализацию для ускорения работы.
Поэтому если поставить на ВМ специальный набор "гостевых" драйверов - ее работа ускорится
и могут появиться новые возможности. Поэтому при выборе ВМ надо учитывать предполагаемую
гостевую ОС и смотреть, какие гостевые драйвера для нее есть.
Обычно считается, что XEN, KVM и VirtualBox быстрее работают когда гостевая ОС -
linux, а WMWare и Hyper-V - когда гостевая ОС - Windows.
4. Последний класс, самый медленный - эмуляторы. Снимают ограничение по архитектуре
процессора (можно эмулировать ARM на x64 или наоборот) - но работают еще медленнее
прошлых классов. Пример - QEMU, BOCHS.
У эмуляторов тоже есть приемы для ускорения выполнения. Но сравнение эмуляторов выходит
уже за рамки этого вопроса.
Доброго всем времени суток!
Попалась тут задача, - сделать свой класс стека, с методами .pop(), .push(digit),
.inc(x, y)
С первыми двумя думаю понятно, а вот inc стоит объяснить, он берет первые x элементов
в массиве и прибавляет к ним число y (соответственно в результате мы должны получить
измененный стек).
Нужно, чтобы запуская этот этот метод на стеке из 100000001 внутри цикла в 100000001,
это дело не зависало и отрабатывало максимально быстро (человек, который проверяет
эту работу, говорит, что можно сделать так, чтобы нижеуказанный тест проходил в пределах
нескольких секунд). Маньяки оптимизации, прийдите!!!
public void StackTest()
{
var watch = new Stopwatch();
var stack = new StackClass();
var count = 100000001;
watch.Start();
for (int i = 0; i < count; i++)
{
stack.Push(i);
}
Console.WriteLine(watch.Elapsed);
watch.Restart();
for (int i = 0; i < count; i++)
{
stack.Inc(i, 2);
}
Console.WriteLine(watch.Elapsed);
watch.Restart();
for (int i = 0; i < count; i++)
{
stack.Pop();
}
Console.WriteLine(watch.Elapsed);
}
Я попробовал несколько реализаций, через List, Collection, Array. Самая быстрая получилась
через массив обычный. Вот такая:
class Stack
{
private int[] arr = new int[100000001];
private int _count;
private int _currentIndex;
public Stack()
{
_count = 0;
_currentIndex = -1;
}
public void Push(int digit)
{
if (IsFull)
{
throw new Exception("Array is full");
}
_currentIndex++;
arr[_currentIndex] = digit;
_count++;
}
public int Pop()
{
int tmp = 0;
if (IsEmpty)
{
throw new Exception("No elements in array");
}
tmp = arr[_currentIndex];
arr[_currentIndex] = default(int);
_count--;
_currentIndex--;
return tmp;
}
public void Inc(int count, int multiplier)
{
if (count > _count)
{
throw new Exception("Not enought element in array");
}
for (int i = 0; i < count; i++)
{
arr[i] += multiplier;
}
Console.WriteLine("Ready");
}
private bool IsEmpty {
get{ return _count == 0; }
}
private bool IsFull {
get { return _currentIndex == arr.Length; }
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Type exit and press enter to quit, type push
to push digit in stack, pop - to see last stack element, inc to
multiply first elements on ");
Stack stack = new Stack();
while (true)
{
string[] input;
string command = "";
input = Console.ReadLine().Split(new char[] { ' ' });
command = input[0];
try
{
switch (command)
{
case "push": { stack.Push(Convert.ToInt32(input[1])); break; }
case "pop": { Console.WriteLine(stack.Pop().ToString()); break; }
case "inc": { stack.Inc(Convert.ToInt32(input[1]), Convert.ToInt32(input[2]));
break; }
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
if (command == "exit") break;
}
}
}
Ответы
Ответ 1
Думаю, правильное решение примерно такое: для каждой позиции в имеющемся массиве
храним значение инкремента, действующее на неё и левее (но не правее). В pop будем
пересчитывать инкремент для текущего элемента. Для этого будем накапливать правые инкременты
в inc, и суммировать накопленное значение с оригинальным в массиве.
http://ideone.com/b3T3ci
public class StackClass
{
private int[] data = new int[100000001];
private int[] add = new int[100000001];
private int inc = 0;
private int i = -1;
public void Push(int x)
{
if (i >= 0) add[i] += inc;
inc = 0;
data[++i] = x;
}
public int Pop()
{
inc += add[i];
add[i] = 0;
return data[i--] + inc;
}
public void Inc(int r, int delta)
{
if (r <= i)
add[r] += delta;
else
inc += delta;
}
}
Успешно #stdin #stdout 1.38s 29672KB
00:00:00.5797480
00:00:00.3059362
00:00:00.4621549
Думаю, можно даже нормально List применить:
http://ideone.com/QTmDnz
public class StackClass
{
private List data = new List(100000001);
private List add = new List(100000001);
private int inc = 0;
public void Push(int x)
{
data.Add(x);
if (add.Count > 0) add[add.Count-1] += inc;
add.Add(0);
inc = 0;
}
public int Pop()
{
int i = data.Count - 1;
int res = data[i] + (inc += add[i]);
add.RemoveAt(i);
data.RemoveAt(i);
return res;
}
public void Inc(int r, int delta)
{
if (r < add.Count)
add[r] += delta;
else
inc += delta;
}
}
Секунд в 6 должна уложиться, но ideone хочет 5:
Превышено ограничение на время #stdin #stdout 5s 29800KB
00:00:02.2248518
00:00:01.1903442
00:00:02.0052423
Более ранняя версия с багами:
http://ideone.com/16JZ5V
Успешно time: 4.67 memory: 29808 signal:0
00:00:01.3654668
00:00:01.1821722
00:00:02.0888570
Ответ 2
Для решения задачи прибавления на сегменте массива используются специальные структуры
данных, или подходы, например:
sqrt-декомпозиция, сложность O(sqrt(n))
дерево отрезков, O(ln(n))
Дерево Фенвика, O(ln(n))
Как реализовать их, можно прочитать, например, здесь http://e-maxx.ru/algo/sqrt_decomposition
Ответ 3
В методе Pop можно не обнулять значение массива и не использовать лишнюю переменную.
Это избыточно. Как
Единственная идея, которая приходит на ум - не производить сложения, а записывать
интервалы, которые требуется изменить. И при попытке обращения к элементу, проходить
все эти запросы и выдавать вычисленный элемент. Следует не забыть, что при извлечении
элементов потребуется так же править интервалы - вы не должны в итоге прибавить число
к элементу, которого не было на момент запроса
Что быстрее - System.currentTimeMillis() или Date().getTime()?
Ответы
Ответ 1
System.currentTimeMillis() чуть быстрее, чем Date().getTime():
long t = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
System.currentTimeMillis();
}
System.out.println("Elapsed: " + (System.currentTimeMillis() - t));
t = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
new Date().getTime();
}
System.out.println("Elapsed: " + (System.currentTimeMillis() - t));
Консоль:
Elapsed: 11623
Elapsed: 11713
А причина этого очень проста, если посмотреть на исходники, то видно, что в конструкторе Date вызывается System.currentTimeMillis() (об этом тут писали):
public Date() {
this(System.currentTimeMillis());
}
Ответ 2
Теоретически System.currentTimeMillis() будет быстрей за счёт того, что не создаетс
новый обьект new Date(), который все равно вызывает System.currentTimeMillis(), но разница будет ничтожна.
Ответ 3
System.currentTimeMillis() - быстрее, просто потому, что new Date() само вызывае
эту функцию. Можете посмотреть это в реализации для OpenJDK. Так же можете посмотреть похожий вопрос на SOen.
В таких языках как Java и C# для конкатенации большого числа строк принято использовать StringBuilder, чтобы получить линейную асимптотику вместо квадратичной.
Однако, JavaScript каким-то образом справляется имея лишь один тип String. Асимптотик
конкатенации там линейная, по крайней мере при циклах до 131072 итераций. Но, что странно, время не зависит от длины складываемых строк. По крайней мере, так происходит в Хроме.
Как так вышло?
И бонусный вопрос знатокам JS: а что собственно случилось при 262144?
http://ideone.com/gtm5iy
using System;
using System.Collections.Generic;
using System.Diagnostics;
public class Program
{
private static string Test(int n, string s)
{
var res = "";
for (var q=0; q[10];
const int N = 1024;
var sw = new Stopwatch();
for (var n=0; n();
foreach (var s in new string[] {"!", "!2", "!234", "!2345678"})
{
res[n][s] = 0;
for (var q=0; qC:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe StringConcat.cs && StringConcat.exe
Microsoft (R) Visual C# Compiler version 4.7.2046.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.
This compiler is provided as part of the Microsoft (R) .NET Framework, but only support
language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240
0 1 0.000 0.000 0.000 0.000
1 2 0.000 0.000 0.000 0.000
2 4 0.000 0.000 0.000 0.000
3 8 0.001 0.001 0.001 0.001
4 16 0.002 0.001 0.001 0.002
5 32 0.002 0.004 0.004 0.004
6 64 0.005 0.006 0.009 0.014
7 128 0.013 0.019 0.028 0.043
8 256 0.042 0.057 0.087 0.148
9 512 0.118 0.174 0.302 0.559
10 1024 0.354 0.606 1.124 2.279
11 2048 1.220 2.242 4.545 10.041
12 4096 4.517 8.982 19.706 41.568
13 8192 17.864 39.063 82.814 169.274
14 16384 78.454 165.893 337.830 718.843
function test(n, s) {
var res = '';
for (var q=0; q
Ответы
Ответ 1
Почему так происходит?
Судя по сорцам V8, строки оптимизированы для конкатенации. При конкатенации вмест
создания новой строки создаётся экземпляр, который ссылается на конкатенируемые куски. Каждый кусок тоже может ссылаться на подкуски. Таким образом строится бинарное дерево.
// The ConsString class describes string values built by using the
// addition operator on strings. A ConsString is a pair where the
// first and second components are pointers to other string values.
// One or both components of a ConsString can be pointers to other
// ConsStrings, creating a binary tree of ConsStrings where the leaves
// are non-ConsString string values. The string value represented by
// a ConsString can be obtained by concatenating the leaf string
// values in a left-to-right depth-first traversal of the tree.
class ConsString : public String {
public:
// First string of the cons cell.
inline String* first();
inline void set_first(String* first,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
// Second string of the cons cell.
inline String* second();
inline void set_second(String* second,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
// Minimum length for a cons string.
static const int kMinLength = 13;
// ...
}
Также есть подклассы строк для подстрок, явно разделяются однобайтовые и двубайтовые строки, и так далее.
// The Sliced String class describes strings that are substrings of another
// sequential string. The motivation is to save time and memory when creating
// a substring. A Sliced String is described as a pointer to the parent,
// the offset from the start of the parent string and the length. Using
// a Sliced String therefore requires unpacking of the parent string and
// adding the offset to the start address. A substring of a Sliced String
// are not nested since the double indirection is simplified when creating
// such a substring.
// Currently missing features are:
// - handling externalized parent strings
// - external strings as parent
// - truncating sliced string to enable otherwise unneeded parent to be GC'ed.
class SlicedString : public String {
public:
inline String* parent();
inline void set_parent(String* parent,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
inline int offset() const;
inline void set_offset(int offset);
// Minimum length for a sliced string.
static const int kMinLength = 13;
// ...
}
В результате такая простая операция как конкатенация превращается в подобное:
MaybeHandle Factory::NewConsString(Handle left,
Handle right) {
if (left->IsThinString()) {
left = handle(Handle::cast(left)->actual(), isolate());
}
if (right->IsThinString()) {
right = handle(Handle::cast(right)->actual(), isolate());
}
int left_length = left->length();
if (left_length == 0) return right;
int right_length = right->length();
if (right_length == 0) return left;
int length = left_length + right_length;
if (length == 2) {
uint16_t c1 = left->Get(0);
uint16_t c2 = right->Get(0);
return MakeOrFindTwoCharacterString(isolate(), c1, c2);
}
// Make sure that an out of memory exception is thrown if the length
// of the new cons string is too large.
if (length > String::kMaxLength || length < 0) {
THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), String);
}
bool left_is_one_byte = left->IsOneByteRepresentation();
bool right_is_one_byte = right->IsOneByteRepresentation();
bool is_one_byte = left_is_one_byte && right_is_one_byte;
bool is_one_byte_data_in_two_byte_string = false;
if (!is_one_byte) {
// At least one of the strings uses two-byte representation so we
// can't use the fast case code for short one-byte strings below, but
// we can try to save memory if all chars actually fit in one-byte.
is_one_byte_data_in_two_byte_string =
left->HasOnlyOneByteChars() && right->HasOnlyOneByteChars();
if (is_one_byte_data_in_two_byte_string) {
isolate()->counters()->string_add_runtime_ext_to_one_byte()->Increment();
}
}
// If the resulting string is small make a flat string.
if (length < ConsString::kMinLength) {
// Note that neither of the two inputs can be a slice because:
STATIC_ASSERT(ConsString::kMinLength <= SlicedString::kMinLength);
DCHECK(left->IsFlat());
DCHECK(right->IsFlat());
STATIC_ASSERT(ConsString::kMinLength <= String::kMaxLength);
if (is_one_byte) {
Handle result =
NewRawOneByteString(length).ToHandleChecked();
DisallowHeapAllocation no_gc;
uint8_t* dest = result->GetChars();
// Copy left part.
const uint8_t* src =
left->IsExternalString()
? Handle::cast(left)->GetChars()
: Handle::cast(left)->GetChars();
for (int i = 0; i < left_length; i++) *dest++ = src[i];
// Copy right part.
src = right->IsExternalString()
? Handle::cast(right)->GetChars()
: Handle::cast(right)->GetChars();
for (int i = 0; i < right_length; i++) *dest++ = src[i];
return result;
}
return (is_one_byte_data_in_two_byte_string)
? ConcatStringContent(
NewRawOneByteString(length).ToHandleChecked(), left, right)
: ConcatStringContent(
NewRawTwoByteString(length).ToHandleChecked(), left, right);
}
bool one_byte = (is_one_byte || is_one_byte_data_in_two_byte_string);
return NewConsString(left, right, length, one_byte);
}
Всякие подобные премудрости — всегда компромисс, обложенный эвристиками. Движок JavaScrip
пытается угадать, когда выгоднее использовать какой способ: когда создавать полноценную строку, когда строить бинарное дерево строк; когда важнее скорость конкатенации, когда важнее скорость итерации; и так далее.
Очевиден плюс: потенциально очень медленные операции становятся более быстрыми. Особенн
важно это для случаев, когда некоторые паттерны использования классов часто используютс
программистами, например, конкатенация строк. Но очевиден и минус: другие операции становятся медленнее, кроме того движок не всегда угадывает намерения программиста и выбирает самый оптимальный способ.
Почему так сделано в JavaScript?
Потому что язык сделан намеренно простым для понимания и использования. Чем больше магии, чем меньше нужно думать о внутреннем представлении, тем проще писать программы.
Кроме того, когда язык создавался, скорость выполнения вовсе не имела значения, потом
что сложных программ на языке никто не писал. Сложные программы появились намного позже. Если вы запустите эту программу не в Chrome последней версии, а в любом браузере 20-летней давности, вы обнаружите совсем другую производительность.
Почему так не сделано в других языках?
Это зависит от языка и его предназначения. Например, строки в C++ простые и топорные
от программиста требуется указывать каждое движение, появляется разница в производительност
между объявлением строки в теле цикла или вне, между передачей строки в функцию по ссылке и по значению, от программиста ожидается указание заранее ожидаемой длины строки и так далее.
С другой стороны, в PHP массивы находятся в роли строк в JS и дадут фору по сложности
Массивы могут быть векторами, словарями, множествами и так далее. Они обладают сложной структурой, которая оптимизирована под каждый случай.
В целом, подход к стандартным классам меняется от языка к языку и даже между версиям
одного компилятора языка. Сегодня в JavaScript и PHP появляются типизированные массивы, завтра может появиться StringBuilder.
Пытаюсь выбрать виртуальную машину, и не могу понять как их сравнивать. Когда читаю описание любой ВМ - вижу много слов про используемые технологии, и почему они делают эту ВМ самой быстрой. Но ведь не могут все они быть самыми быстрыми, какие-то из них же должны быть быстрее других? Как сравнивать разные виртуальные машины?
Ответ
1. Наиболее быстрыми являются виртуальные машины контейнерного типа - все контейнеры используют общее ядро и общие модули уровня ядра, но служебные структуры каждого процесса инициализированы особым образом, что создает иллюзию совершенно разных серверов. Примеры контейнеров - OpenVZ, Docker. Работают только на линуксе, потому что над другими ОС невозможно надругаться настолько же сильно. 2. Следом за контейнерами в порядке уменьшения производительности идет паравиртуализация. Паравиртуализация - это запуск гостевой ОС полностью на уровне пользователя, для этого для нужной ОС готовят специальные драйвера, которые подменяют все те модули ядра, которым ранее требовался доступ к привилегиям уровня ядра (ring 0 на архитектуре x86/64). Пример паравиртуальных машин - XEN, User mode linux. Ограничение - гостевая ОС для работы в паравиртуальном режиме должна быть совместима с используемым гипервизором. Зачастую это ограничение можно читать как "гостевая ОС должна быть линуксом", хотя есть и некоторые исключения. 3. Следующие по тяжести реализации - гипервизоры, использующие аппаратную виртуализацию. Это опять XEN, KVM, VirtualBox, WMWare, Hyper-V и QEMU (в режиме KQEMU)... Здесь уже нет никаких ограничений на гостевую ОС, кроме наличия под нее драйверов для виртуальных устройств. Но многие реализации эмулируют в качестве "виртуальных" реально существующие устройства, что упрощает проблему с драйверами. Но тут надо понимать, что несмотря на возможность полной виртуализации на каждой из этих ВМ, все они могут использовать частичную паравиртуализацию для ускорения работы. Поэтому если поставить на ВМ специальный набор "гостевых" драйверов - ее работа ускорится и могут появиться новые возможности. Поэтому при выборе ВМ надо учитывать предполагаемую гостевую ОС и смотреть, какие гостевые драйвера для нее есть. Обычно считается, что XEN, KVM и VirtualBox быстрее работают когда гостевая ОС - linux, а WMWare и Hyper-V - когда гостевая ОС - Windows. 4. Последний класс, самый медленный - эмуляторы. Снимают ограничение по архитектуре процессора (можно эмулировать ARM на x64 или наоборот) - но работают еще медленнее прошлых классов. Пример - QEMU, BOCHS. У эмуляторов тоже есть приемы для ускорения выполнения. Но сравнение эмуляторов выходит уже за рамки этого вопроса.
Доброго всем времени суток! Попалась тут задача, - сделать свой класс стека, с методами .pop(), .push(digit), .inc(x, y)
С первыми двумя думаю понятно, а вот inc стоит объяснить, он берет первые x элементов в массиве и прибавляет к ним число y (соответственно в результате мы должны получить измененный стек). Нужно, чтобы запуская этот этот метод на стеке из 100000001 внутри цикла в 100000001, это дело не зависало и отрабатывало максимально быстро (человек, который проверяет эту работу, говорит, что можно сделать так, чтобы нижеуказанный тест проходил в пределах нескольких секунд). Маньяки оптимизации, прийдите!!! public void StackTest()
{
var watch = new Stopwatch();
var stack = new StackClass(); var count = 100000001; watch.Start();
for (int i = 0; i < count; i++)
{
stack.Push(i);
}
Console.WriteLine(watch.Elapsed);
watch.Restart(); for (int i = 0; i < count; i++)
{
stack.Inc(i, 2);
}
Console.WriteLine(watch.Elapsed);
watch.Restart(); for (int i = 0; i < count; i++)
{
stack.Pop();
}
Console.WriteLine(watch.Elapsed);
}
Я попробовал несколько реализаций, через List, Collection, Array. Самая быстрая получилась через массив обычный. Вот такая: class Stack
{
private int[] arr = new int[100000001];
private int _count;
private int _currentIndex;
public Stack()
{
_count = 0;
_currentIndex = -1;
} public void Push(int digit)
{
if (IsFull)
{
throw new Exception("Array is full");
}
_currentIndex++;
arr[_currentIndex] = digit;
_count++;
} public int Pop()
{
int tmp = 0;
if (IsEmpty)
{
throw new Exception("No elements in array");
}
tmp = arr[_currentIndex];
arr[_currentIndex] = default(int);
_count--;
_currentIndex--;
return tmp;
} public void Inc(int count, int multiplier)
{
if (count > _count)
{
throw new Exception("Not enought element in array");
}
for (int i = 0; i < count; i++)
{
arr[i] += multiplier;
}
Console.WriteLine("Ready");
} private bool IsEmpty {
get{ return _count == 0; }
}
private bool IsFull {
get { return _currentIndex == arr.Length; }
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Type exit and press enter to quit, type push to push digit in stack, pop - to see last stack element, inc to multiply first elements on ");
Stack stack = new Stack();
while (true)
{
string[] input;
string command = "";
input = Console.ReadLine().Split(new char[] { ' ' });
command = input[0];
try
{
switch (command)
{
case "push": { stack.Push(Convert.ToInt32(input[1])); break; }
case "pop": { Console.WriteLine(stack.Pop().ToString()); break; }
case "inc": { stack.Inc(Convert.ToInt32(input[1]), Convert.ToInt32(input[2])); break; }
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
if (command == "exit") break;
}
}
}
Ответ
Думаю, правильное решение примерно такое: для каждой позиции в имеющемся массиве храним значение инкремента, действующее на неё и левее (но не правее). В pop будем пересчитывать инкремент для текущего элемента. Для этого будем накапливать правые инкременты в inc, и суммировать накопленное значение с оригинальным в массиве. http://ideone.com/b3T3ci public class StackClass
{
private int[] data = new int[100000001];
private int[] add = new int[100000001];
private int inc = 0;
private int i = -1; public void Push(int x)
{
if (i >= 0) add[i] += inc;
inc = 0;
data[++i] = x;
} public int Pop()
{
inc += add[i];
add[i] = 0;
return data[i--] + inc;
} public void Inc(int r, int delta)
{
if (r <= i)
add[r] += delta;
else
inc += delta;
}
}
Успешно #stdin #stdout 1.38s 29672KB
00:00:00.5797480
00:00:00.3059362
00:00:00.4621549
Думаю, можно даже нормально List применить: http://ideone.com/QTmDnz public class StackClass
{
private List data = new List(100000001);
private List add = new List(100000001);
private int inc = 0; public void Push(int x)
{
data.Add(x);
if (add.Count > 0) add[add.Count-1] += inc;
add.Add(0);
inc = 0;
} public int Pop()
{
int i = data.Count - 1;
int res = data[i] + (inc += add[i]); add.RemoveAt(i);
data.RemoveAt(i); return res;
} public void Inc(int r, int delta)
{
if (r < add.Count)
add[r] += delta;
else
inc += delta;
}
}
Секунд в 6 должна уложиться, но ideone хочет 5:
Превышено ограничение на время #stdin #stdout 5s 29800KB
00:00:02.2248518
00:00:01.1903442
00:00:02.0052423
Более ранняя версия с багами:
http://ideone.com/16JZ5V
Успешно time: 4.67 memory: 29808 signal:0
00:00:01.3654668
00:00:01.1821722
00:00:02.0888570
В таких языках как Java и C# для конкатенации большого числа строк принято использовать StringBuilder, чтобы получить линейную асимптотику вместо квадратичной. Однако, JavaScript каким-то образом справляется имея лишь один тип String. Асимптотика конкатенации там линейная, по крайней мере при циклах до 131072 итераций. Но, что странно, время не зависит от длины складываемых строк. По крайней мере, так происходит в Хроме. Как так вышло? И бонусный вопрос знатокам JS: а что собственно случилось при 262144? http://ideone.com/gtm5iy using System;
using System.Collections.Generic;
using System.Diagnostics; public class Program
{
private static string Test(int n, string s)
{
var res = ""; for (var q=0; q return res;
} public static void Main()
{
var res = new Dictionary[10];
const int N = 1024;
var sw = new Stopwatch(); for (var n=0; n(); foreach (var s in new string[] {"!", "!2", "!234", "!2345678"})
{
res[n][s] = 0; for (var q=0; q res[n][s] /= N;
}
} for (var n=0; n foreach (var kvp in res[n])
Console.Write("{0,10:0.000} ", kvp.Value / 1000); Console.WriteLine();
}
}
}
0 1 0.001 0.000 0.000 0.000
1 2 0.002 0.001 0.001 0.001
2 4 0.003 0.003 0.005 0.003
3 8 0.006 0.006 0.007 0.007
4 16 0.013 0.015 0.013 0.014
5 32 0.025 0.025 0.032 0.034
6 64 0.050 0.059 0.070 0.097
7 128 0.120 0.142 0.220 0.303
8 256 0.337 0.417 0.630 0.972
9 512 0.897 1.256 1.964 4.087
К сожалению, при увеличении числа итераций программа не укладывается в 5 секунд, отведённые на выполнение на ideone. Вот результаты с домашнего компа: D:\Temp\Supertemp>C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe StringConcat.cs && StringConcat.exe
Microsoft (R) Visual C# Compiler version 4.7.2046.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved. This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240 0 1 0.000 0.000 0.000 0.000
1 2 0.000 0.000 0.000 0.000
2 4 0.000 0.000 0.000 0.000
3 8 0.001 0.001 0.001 0.001
4 16 0.002 0.001 0.001 0.002
5 32 0.002 0.004 0.004 0.004
6 64 0.005 0.006 0.009 0.014
7 128 0.013 0.019 0.028 0.043
8 256 0.042 0.057 0.087 0.148
9 512 0.118 0.174 0.302 0.559
10 1024 0.354 0.606 1.124 2.279
11 2048 1.220 2.242 4.545 10.041
12 4096 4.517 8.982 19.706 41.568
13 8192 17.864 39.063 82.814 169.274
14 16384 78.454 165.893 337.830 718.843
function test(n, s) {
var res = '';
for (var q=0; q
Ответ
Почему так происходит? Судя по сорцам V8, строки оптимизированы для конкатенации. При конкатенации вместо создания новой строки создаётся экземпляр, который ссылается на конкатенируемые куски. Каждый кусок тоже может ссылаться на подкуски. Таким образом строится бинарное дерево. // The ConsString class describes string values built by using the
// addition operator on strings. A ConsString is a pair where the
// first and second components are pointers to other string values.
// One or both components of a ConsString can be pointers to other
// ConsStrings, creating a binary tree of ConsStrings where the leaves
// are non-ConsString string values. The string value represented by
// a ConsString can be obtained by concatenating the leaf string
// values in a left-to-right depth-first traversal of the tree.
class ConsString : public String {
public:
// First string of the cons cell.
inline String* first();
inline void set_first(String* first,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
// Second string of the cons cell.
inline String* second();
inline void set_second(String* second,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
// Minimum length for a cons string.
static const int kMinLength = 13;
// ...
}
Также есть подклассы строк для подстрок, явно разделяются однобайтовые и двубайтовые строки, и так далее. // The Sliced String class describes strings that are substrings of another
// sequential string. The motivation is to save time and memory when creating
// a substring. A Sliced String is described as a pointer to the parent,
// the offset from the start of the parent string and the length. Using
// a Sliced String therefore requires unpacking of the parent string and
// adding the offset to the start address. A substring of a Sliced String
// are not nested since the double indirection is simplified when creating
// such a substring.
// Currently missing features are:
// - handling externalized parent strings
// - external strings as parent
// - truncating sliced string to enable otherwise unneeded parent to be GC'ed.
class SlicedString : public String {
public:
inline String* parent();
inline void set_parent(String* parent,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
inline int offset() const;
inline void set_offset(int offset);
// Minimum length for a sliced string.
static const int kMinLength = 13;
// ...
}
В результате такая простая операция как конкатенация превращается в подобное MaybeHandle Factory::NewConsString(Handle left,
Handle right) {
if (left->IsThinString()) {
left = handle(Handle::cast(left)->actual(), isolate());
}
if (right->IsThinString()) {
right = handle(Handle::cast(right)->actual(), isolate());
}
int left_length = left->length();
if (left_length == 0) return right;
int right_length = right->length();
if (right_length == 0) return left; int length = left_length + right_length; if (length == 2) {
uint16_t c1 = left->Get(0);
uint16_t c2 = right->Get(0);
return MakeOrFindTwoCharacterString(isolate(), c1, c2);
} // Make sure that an out of memory exception is thrown if the length
// of the new cons string is too large.
if (length > String::kMaxLength || length < 0) {
THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), String);
} bool left_is_one_byte = left->IsOneByteRepresentation();
bool right_is_one_byte = right->IsOneByteRepresentation();
bool is_one_byte = left_is_one_byte && right_is_one_byte;
bool is_one_byte_data_in_two_byte_string = false;
if (!is_one_byte) {
// At least one of the strings uses two-byte representation so we
// can't use the fast case code for short one-byte strings below, but
// we can try to save memory if all chars actually fit in one-byte.
is_one_byte_data_in_two_byte_string =
left->HasOnlyOneByteChars() && right->HasOnlyOneByteChars();
if (is_one_byte_data_in_two_byte_string) {
isolate()->counters()->string_add_runtime_ext_to_one_byte()->Increment();
}
} // If the resulting string is small make a flat string.
if (length < ConsString::kMinLength) {
// Note that neither of the two inputs can be a slice because:
STATIC_ASSERT(ConsString::kMinLength <= SlicedString::kMinLength);
DCHECK(left->IsFlat());
DCHECK(right->IsFlat()); STATIC_ASSERT(ConsString::kMinLength <= String::kMaxLength);
if (is_one_byte) {
Handle result =
NewRawOneByteString(length).ToHandleChecked();
DisallowHeapAllocation no_gc;
uint8_t* dest = result->GetChars();
// Copy left part.
const uint8_t* src =
left->IsExternalString()
? Handle::cast(left)->GetChars()
: Handle::cast(left)->GetChars();
for (int i = 0; i < left_length; i++) *dest++ = src[i];
// Copy right part.
src = right->IsExternalString()
? Handle::cast(right)->GetChars()
: Handle::cast(right)->GetChars();
for (int i = 0; i < right_length; i++) *dest++ = src[i];
return result;
} return (is_one_byte_data_in_two_byte_string)
? ConcatStringContent(
NewRawOneByteString(length).ToHandleChecked(), left, right)
: ConcatStringContent(
NewRawTwoByteString(length).ToHandleChecked(), left, right);
} bool one_byte = (is_one_byte || is_one_byte_data_in_two_byte_string);
return NewConsString(left, right, length, one_byte);
}
Всякие подобные премудрости — всегда компромисс, обложенный эвристиками. Движок JavaScript пытается угадать, когда выгоднее использовать какой способ: когда создавать полноценную строку, когда строить бинарное дерево строк; когда важнее скорость конкатенации, когда важнее скорость итерации; и так далее. Очевиден плюс: потенциально очень медленные операции становятся более быстрыми. Особенно важно это для случаев, когда некоторые паттерны использования классов часто используются программистами, например, конкатенация строк. Но очевиден и минус: другие операции становятся медленнее, кроме того движок не всегда угадывает намерения программиста и выбирает самый оптимальный способ. Почему так сделано в JavaScript? Потому что язык сделан намеренно простым для понимания и использования. Чем больше магии, чем меньше нужно думать о внутреннем представлении, тем проще писать программы. Кроме того, когда язык создавался, скорость выполнения вовсе не имела значения, потому что сложных программ на языке никто не писал. Сложные программы появились намного позже. Если вы запустите эту программу не в Chrome последней версии, а в любом браузере 20-летней давности, вы обнаружите совсем другую производительность. Почему так не сделано в других языках? Это зависит от языка и его предназначения. Например, строки в C++ простые и топорные, от программиста требуется указывать каждое движение, появляется разница в производительности между объявлением строки в теле цикла или вне, между передачей строки в функцию по ссылке и по значению, от программиста ожидается указание заранее ожидаемой длины строки и так далее. С другой стороны, в PHP массивы находятся в роли строк в JS и дадут фору по сложности. Массивы могут быть векторами, словарями, множествами и так далее. Они обладают сложной структурой, которая оптимизирована под каждый случай. В целом, подход к стандартным классам меняется от языка к языку и даже между версиями одного компилятора языка. Сегодня в JavaScript и PHP появляются типизированные массивы, завтра может появиться StringBuilder.
В таких языках как Java и C# для конкатенации большого числа строк принято использовать StringBuilder, чтобы получить линейную асимптотику вместо квадратичной. Однако, JavaScript каким-то образом справляется имея лишь один тип String. Асимптотика конкатенации там линейная, по крайней мере при циклах до 131072 итераций. Но, что странно, время не зависит от длины складываемых строк. По крайней мере, так происходит в Хроме. Как так вышло? И бонусный вопрос знатокам JS: а что собственно случилось при 262144? http://ideone.com/gtm5iy using System;
using System.Collections.Generic;
using System.Diagnostics; public class Program
{
private static string Test(int n, string s)
{
var res = ""; for (var q=0; q return res;
} public static void Main()
{
var res = new Dictionary[10];
const int N = 1024;
var sw = new Stopwatch(); for (var n=0; n(); foreach (var s in new string[] {"!", "!2", "!234", "!2345678"})
{
res[n][s] = 0; for (var q=0; q res[n][s] /= N;
}
} for (var n=0; n foreach (var kvp in res[n])
Console.Write("{0,10:0.000} ", kvp.Value / 1000); Console.WriteLine();
}
}
}
0 1 0.001 0.000 0.000 0.000
1 2 0.002 0.001 0.001 0.001
2 4 0.003 0.003 0.005 0.003
3 8 0.006 0.006 0.007 0.007
4 16 0.013 0.015 0.013 0.014
5 32 0.025 0.025 0.032 0.034
6 64 0.050 0.059 0.070 0.097
7 128 0.120 0.142 0.220 0.303
8 256 0.337 0.417 0.630 0.972
9 512 0.897 1.256 1.964 4.087
К сожалению, при увеличении числа итераций программа не укладывается в 5 секунд, отведённые на выполнение на ideone. Вот результаты с домашнего компа: D:\Temp\Supertemp>C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe StringConcat.cs && StringConcat.exe
Microsoft (R) Visual C# Compiler version 4.7.2046.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved. This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240 0 1 0.000 0.000 0.000 0.000
1 2 0.000 0.000 0.000 0.000
2 4 0.000 0.000 0.000 0.000
3 8 0.001 0.001 0.001 0.001
4 16 0.002 0.001 0.001 0.002
5 32 0.002 0.004 0.004 0.004
6 64 0.005 0.006 0.009 0.014
7 128 0.013 0.019 0.028 0.043
8 256 0.042 0.057 0.087 0.148
9 512 0.118 0.174 0.302 0.559
10 1024 0.354 0.606 1.124 2.279
11 2048 1.220 2.242 4.545 10.041
12 4096 4.517 8.982 19.706 41.568
13 8192 17.864 39.063 82.814 169.274
14 16384 78.454 165.893 337.830 718.843
function test(n, s) {
var res = '';
for (var q=0; q
Ответ
Почему так происходит? Судя по сорцам V8, строки оптимизированы для конкатенации. При конкатенации вместо создания новой строки создаётся экземпляр, который ссылается на конкатенируемые куски. Каждый кусок тоже может ссылаться на подкуски. Таким образом строится бинарное дерево. // The ConsString class describes string values built by using the
// addition operator on strings. A ConsString is a pair where the
// first and second components are pointers to other string values.
// One or both components of a ConsString can be pointers to other
// ConsStrings, creating a binary tree of ConsStrings where the leaves
// are non-ConsString string values. The string value represented by
// a ConsString can be obtained by concatenating the leaf string
// values in a left-to-right depth-first traversal of the tree.
class ConsString : public String {
public:
// First string of the cons cell.
inline String* first();
inline void set_first(String* first,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
// Second string of the cons cell.
inline String* second();
inline void set_second(String* second,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
// Minimum length for a cons string.
static const int kMinLength = 13;
// ...
}
Также есть подклассы строк для подстрок, явно разделяются однобайтовые и двубайтовые строки, и так далее. // The Sliced String class describes strings that are substrings of another
// sequential string. The motivation is to save time and memory when creating
// a substring. A Sliced String is described as a pointer to the parent,
// the offset from the start of the parent string and the length. Using
// a Sliced String therefore requires unpacking of the parent string and
// adding the offset to the start address. A substring of a Sliced String
// are not nested since the double indirection is simplified when creating
// such a substring.
// Currently missing features are:
// - handling externalized parent strings
// - external strings as parent
// - truncating sliced string to enable otherwise unneeded parent to be GC'ed.
class SlicedString : public String {
public:
inline String* parent();
inline void set_parent(String* parent,
WriteBarrierMode mode = UPDATE_WRITE_BARRIER);
inline int offset() const;
inline void set_offset(int offset);
// Minimum length for a sliced string.
static const int kMinLength = 13;
// ...
}
В результате такая простая операция как конкатенация превращается в подобное MaybeHandle Factory::NewConsString(Handle left,
Handle right) {
if (left->IsThinString()) {
left = handle(Handle::cast(left)->actual(), isolate());
}
if (right->IsThinString()) {
right = handle(Handle::cast(right)->actual(), isolate());
}
int left_length = left->length();
if (left_length == 0) return right;
int right_length = right->length();
if (right_length == 0) return left; int length = left_length + right_length; if (length == 2) {
uint16_t c1 = left->Get(0);
uint16_t c2 = right->Get(0);
return MakeOrFindTwoCharacterString(isolate(), c1, c2);
} // Make sure that an out of memory exception is thrown if the length
// of the new cons string is too large.
if (length > String::kMaxLength || length < 0) {
THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), String);
} bool left_is_one_byte = left->IsOneByteRepresentation();
bool right_is_one_byte = right->IsOneByteRepresentation();
bool is_one_byte = left_is_one_byte && right_is_one_byte;
bool is_one_byte_data_in_two_byte_string = false;
if (!is_one_byte) {
// At least one of the strings uses two-byte representation so we
// can't use the fast case code for short one-byte strings below, but
// we can try to save memory if all chars actually fit in one-byte.
is_one_byte_data_in_two_byte_string =
left->HasOnlyOneByteChars() && right->HasOnlyOneByteChars();
if (is_one_byte_data_in_two_byte_string) {
isolate()->counters()->string_add_runtime_ext_to_one_byte()->Increment();
}
} // If the resulting string is small make a flat string.
if (length < ConsString::kMinLength) {
// Note that neither of the two inputs can be a slice because:
STATIC_ASSERT(ConsString::kMinLength <= SlicedString::kMinLength);
DCHECK(left->IsFlat());
DCHECK(right->IsFlat()); STATIC_ASSERT(ConsString::kMinLength <= String::kMaxLength);
if (is_one_byte) {
Handle result =
NewRawOneByteString(length).ToHandleChecked();
DisallowHeapAllocation no_gc;
uint8_t* dest = result->GetChars();
// Copy left part.
const uint8_t* src =
left->IsExternalString()
? Handle::cast(left)->GetChars()
: Handle::cast(left)->GetChars();
for (int i = 0; i < left_length; i++) *dest++ = src[i];
// Copy right part.
src = right->IsExternalString()
? Handle::cast(right)->GetChars()
: Handle::cast(right)->GetChars();
for (int i = 0; i < right_length; i++) *dest++ = src[i];
return result;
} return (is_one_byte_data_in_two_byte_string)
? ConcatStringContent(
NewRawOneByteString(length).ToHandleChecked(), left, right)
: ConcatStringContent(
NewRawTwoByteString(length).ToHandleChecked(), left, right);
} bool one_byte = (is_one_byte || is_one_byte_data_in_two_byte_string);
return NewConsString(left, right, length, one_byte);
}
Всякие подобные премудрости — всегда компромисс, обложенный эвристиками. Движок JavaScript пытается угадать, когда выгоднее использовать какой способ: когда создавать полноценную строку, когда строить бинарное дерево строк; когда важнее скорость конкатенации, когда важнее скорость итерации; и так далее. Очевиден плюс: потенциально очень медленные операции становятся более быстрыми. Особенно важно это для случаев, когда некоторые паттерны использования классов часто используются программистами, например, конкатенация строк. Но очевиден и минус: другие операции становятся медленнее, кроме того движок не всегда угадывает намерения программиста и выбирает самый оптимальный способ. Почему так сделано в JavaScript? Потому что язык сделан намеренно простым для понимания и использования. Чем больше магии, чем меньше нужно думать о внутреннем представлении, тем проще писать программы. Кроме того, когда язык создавался, скорость выполнения вовсе не имела значения, потому что сложных программ на языке никто не писал. Сложные программы появились намного позже. Если вы запустите эту программу не в Chrome последней версии, а в любом браузере 20-летней давности, вы обнаружите совсем другую производительность. Почему так не сделано в других языках? Это зависит от языка и его предназначения. Например, строки в C++ простые и топорные, от программиста требуется указывать каждое движение, появляется разница в производительности между объявлением строки в теле цикла или вне, между передачей строки в функцию по ссылке и по значению, от программиста ожидается указание заранее ожидаемой длины строки и так далее. С другой стороны, в PHP массивы находятся в роли строк в JS и дадут фору по сложности. Массивы могут быть векторами, словарями, множествами и так далее. Они обладают сложной структурой, которая оптимизирована под каждый случай. В целом, подход к стандартным классам меняется от языка к языку и даже между версиями одного компилятора языка. Сегодня в JavaScript и PHP появляются типизированные массивы, завтра может появиться StringBuilder.