Чому Requ_once так погано використовувати?


143

Все, що я читав про кращі практики кодування PHP, постійно говорить, що не використовувати require_once через швидкість.

Чому це?

Який правильний / кращий спосіб зробити те саме, що і require_once? Якщо це має значення, я використовую PHP 5.


9
Зараз це питання досить старе, і відповіді вже сумнівно актуальні. Було б чудово побачити оновлений набір відповідей від учасників :)
Purefan

Відповіді:


108

require_onceі include_onceобидва вимагають, щоб система вела журнал того, що вже було включено / потрібно. Кожен *_onceдзвінок означає перевірку цього журналу. Так що, безумовно, є деякі додаткові роботи там робиться , але досить , щоб збиток швидкості всього програми?

... Я дуже сумніваюся в цьому ... Хіба що, якщо ви не користуєтесь справді старим обладнанням чи багато цього робите .

Якщо будуть робити тисячі *_once, ви могли б зробити роботу самостійно в легшій формі. Для простих додатків, тільки переконавшись , що ви включені тільки він один раз повинно вистачити , але якщо ви все ще отримуєте помилки перевизначати, ви могли б що - щось на зразок цього:

if (!defined('MyIncludeName')) {
    require('MyIncludeName');
    define('MyIncludeName', 1);
}

Я особисто буду дотримуватися *_onceтверджень, але на дурному мільйонному орієнтирі ви можете побачити різницю між цими двома:

                php                  hhvm
if defined      0.18587779998779     0.046600103378296
require_once    1.2219581604004      3.2908599376678

10-100 × повільніше, require_onceі цікаво, що require_onceце здається повільніше hhvm. Знову ж таки, це стосується вашого коду, лише якщо ви працюєте *_onceтисячі разів.


<?php // test.php

$LIMIT = 1000000;

$start = microtime(true);

for ($i=0; $i<$LIMIT; $i++)
    if (!defined('include.php')) {
        require('include.php');
        define('include.php', 1);
    }

$mid = microtime(true);

for ($i=0; $i<$LIMIT; $i++)
    require_once('include.php');

$end = microtime(true);

printf("if defined\t%s\nrequire_once\t%s\n", $mid-$start, $end-$mid);

<?php // include.php

// do nothing.

29
Я сумніваюся, що ваш визначений () метод швидше, ніж вбудована таблиця пошуку, але я погоджуюся з вашим загальним моментом - безумовно, це не проблема ?!
Боббі Джек

1
Я досить впевнений, що ти маєш рацію, Бобі, але я не виступаю за визначення, що стосуються _once. Це просто варіант. Час, який знадобиться для інтерпретації коду, може навіть зробити його незначно повільнішим, але, якщо говорити, я не знаю, наскільки ретельним є внутрішній метод. Це може зробити додаткову роботу, щоб не дублювати копії.
Олі

8
Інший недолік - APC не кешує include_once та Requ_once дзвінки IIRC
dcoseineau

3
Я щойно зробив дуже базовий тест з двох методів - я зробив 1 000 000 ітерацій, включаючи файл, який просто визначив постійну "testinclude" для істини. У першому тесті я використовував Requ_once, у другому я використовував, якщо (! Визначено ('testinclude')) і результати були цікаві: Потрібно: 0,81639003753662 Не визначено: 0,17906713485718 Визначено, швидше 0,63732290267944 мікросекунд.
Тревіс Вестон,

Випадок із визначенням буде швидшим, оскільки ви не виконуєте додаткових перевірок, таких як використання realpath. Це не порівняння двох речей, які справді однакові.
jgmjgm

150

Цей потік змушує мене дуріти, тому що там вже було розміщено "рішення", і це, для всіх намірів і цілей, неправильно. Перерахуємо:

  1. Визначає, що це дуже дорого в PHP. Ви можете переглянути його або перевірити самостійно, але єдиний ефективний спосіб визначення глобальної константи в PHP - це через розширення. (Константи класу є насправді досить пристойними показниками продуктивності, але це суперечка через 2)

  2. Якщо ви використовуєте require_once()належним чином, тобто для включення класів, вам навіть не потрібне визначення; просто перевірити, чи class_exists('Classname'). Якщо файл, до якого ви входите, містить код, тобто ви використовуєте його в процедурному порядку, немає абсолютно жодної причини, яка require_once()повинна бути необхідною для вас; щоразу, коли ви включаєте файл, який, на вашу думку, здійснює виклик підпрограми.

Тому деякий час дуже багато людей використовували class_exists()метод для своїх включень. Мені це не подобається, тому що це нечітко, але вони мали вагомі причини: require_once()були досить неефективними перед деякими з останніх версій PHP. Але це було виправлено, і я стверджую, що додатковий байт-код, який вам доведеться скласти для умовного, та виклик додаткового методу, значно перевершить будь-яку внутрішню перевірку хештебля.

Тепер, визнання: цей матеріал важко перевірити, оскільки на нього припадає так мало часу на виконання.

Ось питання, над яким ви повинні задуматися: включає, як правило, дорогі PHP, тому що кожного разу, коли інтерпретатор потрапляє на нього, він повинен переключитися в режим розбору, генерувати опкоди, а потім стрибати назад. Якщо у вас є 100+ включає, це, безумовно, матиме ефективність. Причина того, що використовувати або не використовувати Requ_once є таким важливим питанням, це тому, що це ускладнює життя кешів опкодів. Пояснення цьому можна знайти тут, але то , що це зводиться до того, що:

  • Якщо під час розбору ви точно знаєте, що включає файли, які вам знадобляться протягом усього життя запиту, require()ті , що знаходяться на самому початку, та кеш-код опкоду будуть обробляти все, що вам потрібно.

  • Якщо ви не використовуєте кеш-код опкоду, ви знаходитесь у важкому місці. Об’єднання всіх ваших включень в один файл (не робіть цього під час розробки, тільки у виробництві), безумовно, може допомогти розібратися в часі, але це болісно, ​​а також вам потрібно точно знати, що ви будете включати під час запит.

  • Автозавантаження дуже зручно, але повільно, з тієї причини, що логіку автозавантаження потрібно запускати щоразу, коли робиться включення. На практиці я виявив, що автозавантаження декількох спеціалізованих файлів за один запит не викликає надто великих проблем, але ви не повинні автоматично завантажувати всі потрібні файли.

  • Якщо у вас, можливо, 10 включень (це дуже зворотний бік обчислення конвертів), все це не хочеться: просто оптимізуйте запити до вашої бази даних чи щось.


14
Це 4 роки, і здебільшого більше не застосовується define(), require_once()і defined()всі займають приблизно 1-2 мікросекунди на кожній моїй машині.
Даніель Бердслі

72
Але це на 2 мікросекунди раніше користувач отримає сторінку. Понад рік перегляду сторінок, який може врятувати користувача цілих 3 секунди! Вони могли дивитися одну десяту частину реклами в той час! Подумайте про користувача. Не витрачайте мікросекунди.
Ендрю Енслі

15
Просто так всі знають про сарказм, мікросекунда - 1/1000000 секунди.
Енді Чейз

1
@AndrewEnsley Ти просто помилився з усім своїм сарказмом. Ви не знаєте про те, що PHP також працює на мікропроцесорах, 1 мікросекунда на вашому ПК - це кілька мілісекунд на мікропроцесорі. А що з 20-ти файлами, більшим проектом? Це затримка в 20 разів за кілька мілісекунд, тож ми вже досягаємо точки, помітної для людини. Якщо цей скрипт викликається часто, це спричинить проблеми з продуктивністю в системі. Оптимізація - це не жарт, і весь світ не обертається навколо вашого ПК. У використанні є десять тисяч процесорів.
Джон

2
@John. Це був анекдот, зроблений з гарним настроєм. Жодної злоби не призначено. Якщо вам варто, щоб оптимізувати свої включення, йдіть прямо вперед.
Ендрю Енслі

66

Мені стало цікаво і перевірив посилання Адама Бекстрома на Tech Your Universe . У цій статті описано одну з причин, за якою потрібно використовувати замість Requ_once. Однак їхні претензії не дотрималися мого аналізу. Мені було б цікаво подивитися, де я, можливо, неправильно проаналізував рішення. Я використовував PHP 5.2.0 для порівняння.

Я почав, створивши 100 файлів заголовків, які використовували Requ_once для включення іншого файлу заголовка. Кожен із цих файлів виглядав приблизно так:

<?php
    // /home/fbarnes/phpperf/hdr0.php
    require_once "../phpperf/common_hdr.php";

?>

Я створив їх за допомогою швидкого хаку Bash:

for i in /home/fbarnes/phpperf/hdr{00..99}.php; do
    echo "<?php
    // $i" > $i
    cat helper.php >> $i;
done

Таким чином я міг легко обмінятися між використанням Requ_once і вимагати, включаючи файли заголовка. Потім я створив app.php для завантаження ста файлів. Це виглядало так:

<?php
    // Load all of the php hdrs that were created previously
    for($i=0; $i < 100; $i++)
    {
        require_once "/home/fbarnes/phpperf/hdr$i.php";
    }

    // Read the /proc file system to get some simple stats
    $pid = getmypid();
    $fp = fopen("/proc/$pid/stat", "r");
    $line = fread($fp, 2048);
    $array = split(" ", $line);

    // Write out the statistics; on RedHat 4.5 with kernel 2.6.9
    // 14 is user jiffies; 15 is system jiffies
    $cntr = 0;
    foreach($array as $elem)
    {
        $cntr++;
        echo "stat[$cntr]: $elem\n";
    }
    fclose($fp);
?>

Я протиставляв заголовки Requ_once із заголовками вимагають, які використовували файл заголовка таким чином:

<?php
    // /home/fbarnes/phpperf/h/hdr0.php
    if(!defined('CommonHdr'))
    {
        require "../phpperf/common_hdr.php";
        define('CommonHdr', 1);
    }
?>

Я не знайшов великої різниці, коли запускав цю програму з вимогою по відношенню до вимоги вимагаю Насправді, мої початкові тести, здавалося, означали, що Requ_once був трохи швидшим, але я не обов'язково вірю в це. Я повторив експеримент із 10000 вхідними файлами. Тут я побачив стійку різницю. Я проводив тест кілька разів, результати є близькими, але використовуючи Requ_once використовує в середньому 30,8 магістральних користувачів і 72,6 системних джиффів; використовуючи потребує використання в середньому 39,4 користувальницьких джифф і 72.0 системних джиффі. Тому виявляється, що навантаження дещо нижча за допомогою Requ_once. Однак час настінного годинника трохи збільшений. Для виклику 10 000 посилань на вимогу в середньому використовується 10,15 секунди, а 10 000 - в середньому 9,84 секунди.

Наступний крок - розглянути ці відмінності. Я використовував strace для аналізу системних викликів, які здійснюються.

Перед відкриттям файлу з Requ_once проводяться наступні системні дзвінки:

time(NULL)                              = 1223772434
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=88, ...}) = 0
time(NULL)                              = 1223772434
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3

Це контрастує з вимогою:

time(NULL)                              = 1223772905
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=146, ...}) = 0
time(NULL)                              = 1223772905
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3

Технологія Вашого Всесвіту означає, що requ_once повинен здійснювати більше дзвінків lstat64. Однак вони обидва роблять однакову кількість дзвінків lstat64. Можливо, різниця полягає в тому, що я не запускаю APC для оптимізації коду вище. Однак далі я порівняв вихід страйсу на всі прогони:

[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
  190709 strace_1000r.out
  210707 strace_1000ro.out
  401416 total

Ефективно існує приблизно ще два системні виклики на файл заголовка при використанні Requ_once. Одна з різниць полягає в тому, що Requ_once має додатковий виклик функції time ():

[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20009
strace_1000ro.out:30008

Інший системний виклик - getcwd ():

[fbarnes@myhost phpperf]$ grep -c getcwd strace_1000r.out strace_1000ro.out
strace_1000r.out:5
strace_1000ro.out:10004

Це називається тому, що я вирішив відносний шлях, на який посилається у файлах hdrXXX. Якщо я роблю це абсолютним посиланням, то єдиною різницею є додатковий виклик часу (NULL), здійснений у коді:

[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
  190705 strace_1000r.out
  200705 strace_1000ro.out
  391410 total
[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20008
strace_1000ro.out:30008

Це, мабуть, означає, що ви могли зменшити кількість системних дзвінків, використовуючи абсолютні шляхи, а не відносні шляхи. Єдина відмінність поза цим - це час (NULL) дзвінків, які, здається, використовуються для інструментації коду для порівняння того, що швидше.

Ще одне зауваження полягає в тому, що пакет оптимізації APC має опцію "apc.include_once_override", яка стверджує, що вона зменшує кількість системних викликів, здійснених за допомогою call_once та включає виклики_once (див. Документацію PHP ).


6
І будь-яка «оптимізація», яку вам доведеться пробігти 10000 разів, щоб побачити таку мізерну різницю, навіть не варта турбуватися. Скористайтеся профілером і дізнайтеся, де у вашій програмі справжні вузькі місця. Сумніваюсь, це питання є вузьким місцем.
DGM

1
Що все це означає, що це зовсім не має значення. Логічно використовуйте все, що працює для вас.
Буттер Буткус

wat are
jiffies

21

Чи можете ви надати нам будь-які посилання на ці методи кодування, які говорять про те, щоб цього не робити? Що стосується мене, то це повна проблема . Я не дивився на вихідний код сам, але я б собі уявити , що єдина відмінність між includeі в include_onceтому , що include_onceдодає , що ім'я файлу в масив і перевіряє через масив кожен раз. Буде легко сортувати цей масив відсортованим, тому пошук по ньому повинен бути O (log n), і навіть додаток середнього розміру повинен мати лише пару десятків.


один з них - chazzuka.com/blog/?p=163 вони насправді не робили "не", але занадто багато "дорогих" речей складаються. і насправді всі включені / потрібні файли додаються до внутрішнього масиву (є функція його повернення), я думаю, що _once доведеться циклікувати цей масив і зробити strcmp, який додав би
Uberfuzzy

7

Кращий спосіб робити речі - це використовувати об'єктно-орієнтований підхід і використовувати __autoload () .


3
але найперший приклад на сторінці об’єктів автозавантаження, яку ви пов’язали з використанням
requ_once

Я цього не купую. Є МНОГО ситуацій, коли OO не підходить так належним чином, як інші парадигми, тому не варто змушувати його просто отримувати будь-які мініатюрні переваги, які можуть бути з __autoload ().
Боббі Джек

1
Ви можете подумати, що автоматичне завантаження насправді займе більше часу, ніж * _once (припускаючи, що ви вимагаєте лише те, що вам потрібно).
nickf

Ні, це не так, принаймні не точно, Автозавантаження все одно потрібно якось включати, і це остання можливість для PHP перед тим, як помилка вийде з ладу - тому насправді PHP насправді виконує потенційно непотрібні перевірки через усі місця, які застосовуються для включення / вимагання та ПІСЛЯ ЩО це закликає автозавантаження (якщо таке визначено) ... PS: __autoload()не рекомендується, і це може бути застарілим у майбутньому, ви повинні використовувати spl_autoload_register(...)ці дні ... PS2: не зрозумійте мене неправильно, я іноді використовую функцію автозавантаження; )
jave.web

6

Це не використання функції, яка погана. Це неправильне розуміння того, як і коли його використовувати, в загальній кодовій базі. Я просто додам трохи більше контексту до цього, можливо, неправильно зрозумілого поняття:

Люди не повинні думати, що Requ_once - це повільна функція. Ви повинні так чи інакше включити свій код. require_once()проти require()швидкості - це не проблема. Йдеться про ефективність, що перешкоджає застереженням, які можуть призвести до сліпого використання. Якщо використовувати його широко, не враховуючи контекст, це може призвести до величезної трати пам’яті або марного коду.

Я бачив, що це дуже погано - це коли величезні монолітні рамки використовують require_once()усі неправильні способи, особливо в складних об'єктно-орієнтованих (OO) середовищах.

Візьмемо приклад використання require_once()у верхній частині кожного класу, як це спостерігається у багатьох бібліотеках:

require_once("includes/usergroups.php");
require_once("includes/permissions.php");
require_once("includes/revisions.php");
class User{
  // User functions
}

Тож Userклас призначений для використання всіх трьох інших класів. Досить справедливо!

Але що робити, якщо відвідувач переглядає сайт і навіть не входить у систему та завантажує рамки: require_once("includes/user.php");для кожного окремого запиту.

Він включає 1 + 3 зайвих класи, які він ніколи не використовуватиме під час конкретного запиту. Ось так роздуті рамки в кінцевому підсумку використовують 40 Мб на запит, на відміну від 5 Мб або менше.


Інші способи, якими він може бути використаний, - це коли клас повторно використовується багатьма іншими! Скажімо, у вас близько 50 класів, які використовують helperфункції. Щоб переконатися, що helpersвони доступні для тих класів, коли вони завантажуються, ви отримуєте:

require_once("includes/helpers.php");
class MyClass{
  // Helper::functions(); // etc..
}

Тут немає нічого поганого. Однак якщо один запит на сторінку включає 15 подібних класів. Ви працюєте require_once15 разів або для хорошого візуального:

require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");

Використання Requ_once () технічно впливає на продуктивність для виконання цієї функції 14 разів, крім того, щоб розібрати ці непотрібні рядки. Маючи лише 10 інших високо використовуваних класів із подібною проблемою, він може нараховувати 100+ рядків такого досить безглуздого повторюваного коду.

З цим, напевно require("includes/helpers.php");, замість цього варто скористатись у завантажувальній програмі вашої програми чи структури. Але оскільки все відносно, все залежить від того, helpersчи варто важити в порівнянні з частотою використання класу 15-100 рядків require_once(). Але якщо ймовірність не використовувати helpersфайл у будь-якому заданому запиті відсутня, то requireнатомість слід бути у вашому головному класі. Перебування require_onceв кожному класі окремо стає марною тратою ресурсів.


require_onceФункція корисна , коли необхідно, але це не слід розглядати як монолітне рішення використовувати всюди для завантаження всіх класів.


5

Вікі PEAR2 (коли вона існувала) використовувалась для перерахування вагомих причин відмови від усіх вимог / включення директив на користь автозавантаження , принаймні щодо коду бібліотеки. Вони прив'язують вас до жорстких структур каталогів, коли альтернативні моделі упаковки, такі як phar, знаходяться на горизонті.

Оновлення: Оскільки веб-архівована версія вікі є некрасивою для очей, я скопіював найважливіші причини нижче:

  • include_path необхідний для використання пакету (PEAR). Це ускладнює поєднання пакета PEAR в іншій програмі з його власним include_path, створення єдиного файлу, що містить необхідні класи, переміщення пакета PEAR в архів phar без великої модифікації вихідного коду.
  • коли верхній рівень Requ_once змішується з умовним Requ_once, це може призвести до неможливості кеш-коду кешами опкодів, такими як APC, які будуть в комплекті з PHP 6.
  • відносно Requ_once вимагає, щоб include_path вже був налаштований на правильне значення, що робить неможливим використання пакета без належного include_path

5

Ці *_once()функції стат кожен батьківський каталог для забезпечення файл , який ви в тому числі не те ж саме , як той , який вже був включений. Це частина причини сповільнення.

Я рекомендую використовувати такий інструмент, як Siege, для проведення бенчмаркінгу. Ви можете спробувати всі запропоновані методології та порівняти часи відповідей.

Більше про те, що require_once()знаходиться у Tech Your Universe .


Дякуємо за вказівник до статті. Requ_once () - це хороший пояс безпеки щодо файлів, що містять подвійні файли, і ми продовжуватимемо його використовувати, але можливість очистити його добре.
Енді Лестер

2

Навіть якщо require_onceі include_once є повільніше , ніж requireта include(або все , що може існувати альтернативи), ми говоримо про маленького рівні мікро-оптимізації тут. Ваш час набагато краще витратити на оптимізацію цього погано написаного циклу чи запиту до бази даних, ніж на занепокоєння з приводу чогось подібного require_once.

Тепер, можна зробити аргумент, що говорить про require_onceпогані практики кодування, оскільки вам не потрібно звертати увагу на те, щоб ваші включення були чистими та організованими, але це не має нічого спільного з самою функцією особливо не з її швидкістю.

Очевидно, що автоматичне завантаження краще заради чистоти коду та простоти обслуговування, але я хочу дати зрозуміти, що це не має нічого спільного зі швидкістю .


0

Ви протестуєте, використовуючи альтернативу oli, альтернативу oli та __autoload (); і протестуйте його чимось на зразок APC встановивши .

Сумніваюсь, що використання константи пришвидшить справи.


0

Так, це трохи дорожче, ніж звичайне ol 'вимагає (). Я думаю, що справа в тому, якщо ви можете тримати свій код достатньо організованим, щоб не дублювати включення, не використовуйте функції * _once (), оскільки це заощадить певні цикли.

Але використання функцій _once () не вбиває вашу програму. В основному, просто не використовуйте це як привід для того, щоб не потрібно організовувати свої включення . У деяких випадках використання його все ще неминуче, і це не є великою справою.


-2

Я думаю, що в документації PEAR є рекомендація щодо вимагати, Requ_once, включати і include_once. Я дотримуюся цієї настанови. Ваша заявка була б більш зрозумілою.


Деякі посилання були б в порядку.
Пітер Мортенсен

-3

Це не має нічого спільного зі швидкістю. Йдеться про те, що не вдасться витончено.

Якщо поправка Requ_once () не вдається, ваш сценарій буде виконано. Більше нічого не обробляється. Якщо ви використовуєте include_once (), решта вашого сценарію намагатиметься надалі рендерірувати, тому потенційно ваші користувачі не будуть мудрішими щодо того, що у вашому сценарії не вдалося.


1
Не обов'язково. Ви можете фактично зачепитися за оброблювача помилок або на обробці вимкнення, щоб надати користувачеві гарну сторінку помилок (хоча люди це рідко роблять). Як розробник, я б набагато швидше щось помилився негайно.
Едвард З. Ян

1
Або, в залежності від обставин, не виходить з ладу - якщо якийсь життєвий файл не вимагає () належним чином, добре б просто відмовитися і зупинити. Але це вимагають vs include, тоді як я думаю, що питання більш орієнтовано на вимагає vs Requiron.
HoboBen

-4

Моя особиста думка полягає в тому, що використання Requ_once (або include_once) є поганою практикою, тому що Requ_once перевіряє для вас, якщо ви вже включили цей файл і пригнічуєте помилки подвійних файлів, що включаються, що призводять до фатальних помилок (наприклад, дублювання декларацій функцій / класів / тощо). .

Ви повинні знати, чи потрібно додати файл.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.