Профілювання конкуренції за ресурси при паралельній обробці в Visual Studio 2010, HTML, XML, DHTML, Інтернет-технології, статті

У міру розповсюдження багатоядерних процесорів розробники ПЗ переключаються на написання багатопоточних додатків, здатних використовувати переваги додаткових обчислювальних ресурсів для більшої продуктивності. Використовуючи міць паралельно виконуваних потоків, ви можете розбити загальну роботу на окремі завдання і виконувати їх паралельно.


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


Синхронізація часто здійснюється за рахунок застосування загальних синхронизирующих об'єктів; в цьому випадку потік, захоплюючий об'єкт, отримує або розділяється, або монопольний доступ до чутливого коду або даних. Коли ресурс більше не потрібен, цей потік звільняє зайнятий їм синхронізуючий об'єкт, і інші потоки можуть спробувати отримати до нього доступ. Залежно від типу синхронізації одночасні запити на захоплення об'єкта можуть призвести до того, що до загального ресурсу буде звертатися відразу кілька потоків або, навпаки, деякі з потоків будуть блоковані до тих пір, поки об'єкт не звільнить попередній потік. Приклади синхронизирующих об'єктів включають критичні секції в C / C + +, використовують процедури доступу EnterCriticalSection і LeaveCriticalSection, функцію WaitForSingleObject в C / C + +, а також вираз lock і клас Monitor в C #.


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


Засоби аналізу продуктивності в Visual Studio 2010 підтримують новий метод – профілювання конкуренції за ресурси (resource contention profiling), який допомагає виявляти конкуренцію паралельних потоків. Відмінний огляд цієї функціональності див Джона Роббінса (John Robbins) в блозі Wintellect


У цій статті я покроково опишу, як виконувати профілювання конкуренції, і поясню сенс даних, які можна збирати за допомогою Visual Studio 2010 IDE і утиліт командного рядка. Я також покажу, як аналізувати ці дані в Visual Studio 2010, і ви побачите, як перемикатися з одного подання аналітичної інформації в інше в процесі вивчення конкуренції. Потім я внесу правки в код прикладу, і ми порівняємо результати профілювання модифікованої програми з результатами вихідної, щоб переконатися в тому, що правки дозволили зменшити інтенсивність конкуренції.


Почнемо з проблеми


Як приклад я візьму те ж додаток для перемножування матриць, яким скористався Хасим Шафи (Hazim Shafi) в своїй статті в блозі "Performance Pattern 1: Identifying Lock Contention" (blogs.msdn.com/hshafi/archive/2009/06/19 / performance-pattern-1-identifying-lock-contention.aspx). Код прикладу написаний на C + +, але обговорювані мною концепції одно застосовні до керованого коду.


Ця програма-приклад використовує декілька потоків для перемножування двох матриць. Кожен потік отримує свою порцію роботи і виконує наступний фрагмент коду:

Копіювати код

for (i = myid*PerProcessorChunk;
i < (myid+1)*PerProcessorChunk;
i++) {
EnterCriticalSection(&mmlock);
for (j=0; j<SIZE; j++) {
for (k=0; k<SIZE; k++) {
C[i][j] += A[i][k]*B[k][j];
}
}
LeaveCriticalSection(&mmlock);
}

Кожен потік має свій ідентифікатор (myid) і відповідає за обчислення ряду рядків (однієї або декількох) у кінцевій матриці C, використовуючи як введення матриці A і B. Уважне вивчення коду показало, що в ньому немає по-справжньому інтенсивних спроб записи в загальний ресурс і кожен потік пише в інший рядок C. Проте розробник вирішив захистити присвоювання матриці критичної секцією. І я повинен подякувати йому за це, тому що тим самим я отримав можливість продемонструвати нові інструменти аналізу продуктивності в Visual Studio 2010 і показати, наскільки легко тепер знаходити надлишкову синхронізацію.


Профілювання набору даних


Припускаючи, що у вас вже є проект Visual Studio з показаним раніше кодом (хоча він не обов'язковий, оскільки ви можете підключити засіб профілювання до будь виконуваному додатку), ви приступаєте до профілізації конкуренції вибором Launch Performance Wizard з меню Analyze.


На першій сторінці цього майстра (рис. 1) Виберіть Concurrency і встановіть прапорець Collect resource contention data. Зауважте, що профілювання конкуренції при паралельній обробці працює в будь-якої версії Windows. Проте використання параметра, пов'язаного з прапорцем Visualize the behavior of a multithreaded application, вимагає роботи в Windows Vista або Windows 7.



Рис. 1. Включення профілювання конкуренції при паралельній обробці


На другій сторінці майстра переконайтеся, що в якості цільового обраний поточний проект. На останній сторінці перевірте, щоб було встановлено прапорець Launch profiling after the wizard finishes і клацніть кнопку Finish. Додаток запуститься під управлінням засоби профілювання. По завершенні файл даних профілювання з'явиться у вікні Performance Explorer (рис. 2).



Рис. 2. Файл результатів профілювання показується у вікні Performance Explorer


Звіт про профілюванні автоматично відкривається в Visual Studio і в поданні Summary показуються результати аналізу роботи програми (рис. 3).



Рис. 3. Подання Summary звіту про профілювання


Аналіз даних профілювання


Синхронізація не завжди викликає конкуренцію потоків на блокування. Якщо блокування доступна, спроба її захопити не блокує виконання потоку і ніякої конкуренції не виникає. У режимі Resource Contention Profiling засіб профілювання збирає дані тільки по синхронизирующим подій, які викликають конкуренцію, і не повідомляє про успішні захопленнях (без блокування). Якщо у вашому додатку взагалі немає конкуренції, ви не отримаєте ніяких даних. Ну а якщо ви отримали якісь дані, значить, у вашому додатку є конкуренція за блокування.


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


Відкривши файл, ви першим ділом бачите уявлення Summary (рис. 3) З трьома основними областями, які можна використовувати для експрес-діагностики.



  1. Діаграма конкуренції показує інтенсивність конкуренції в секунду, відкладену по осі часу виконання вашої програми. Ви можете візуально вивчити піки конкуренції або вибрати часовий інтервал і або збільшити його деталізацію, або включити фільтрацію результатів. При фільтрації здійснюється повторний аналіз даних і видаляються всі дані за межами обраного інтервалу.

  2. У таблиці Most Contended Resources перераховуються ресурси, що викликали найбільш інтенсивну конкуренцію.

  3. У таблиці Most Contended Threads перераховуються потоки з найвищими показниками конкуренції. Тут як критерій використовується не тривалість конкуренції, а її частота. Отже, у вас може бути потік, тривало блокований на якомусь ресурсі, але він не буде показаний в поданні Summary. З іншого боку, потік, що зазнає часту, але короткочасну конкуренцію, за якої він блокується на дуже короткий проміжок, обов'язково з'явиться в цьому поданні.

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


Наприклад, на рис. 3 видно, що Critical Section 1 відповідальна майже за всю конкуренцію в додатку (99,90%). Давайте уважно проаналізуємо цей ресурс.


Імена ресурсів і ідентифікатори потоків в поданні Summary є гіперпосиланнями. Клацнувши Critical Section 1, ви перемкнетеся до подання Resource Details (рис. 4), Де контекст встановлений для конкретного ресурсу – Critical Section 1.



Рис. 4. Подання Resource Details


Resource Details


У верхній частині подання Resource Details показана тимчасова діаграма, на якій кожна горизонтальна лінія відповідає одному потоку. Лінії позначаються кореневої функцією потоку, якщо тільки ви не називаєте керовані потоки в своєму коді (наприклад, використовуючи в C # властивість System.Threading.Thread.Name). Блоки на цій лінії представляють конкуренцію потоку за ресурс, а довжина блоку – тривалість конкуренції. Блоки з різних ліній можуть перекриватися за часом, і це означає, що на ресурсі одночасно блокується кілька потоків.


Лінія Total має особливий сенс. Вона не належить до якогось потоку, а відображає всю конкуренцію всіх потоків за даний ресурс (фактично на цю лінію проектуються всі блоки конкуренції). Як бачите, Critical Section 1 вельми зайнятий ресурс – на його лінії Total немає жодного вільного проміжку.


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


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


Function Details


У нижній частині подання Resource Details знаходиться стек викликів для конкретного блоку конкуренції – дані в ньому показуються тільки після того, як ви виберете якийсь блок. Ви також можете просто затримати курсор миші над блоком, не клацаючи його, і з'явиться спливаюче вікно зі стеком і тривалістю конкуренції.


Як видно з стека викликів, в ньому присутня MatMult – одна з функцій програми-прикладу, а значить, вона була причиною конкуренції. Щоб визначити, який рядок в коді функції відповідальна за конкуренцію, двічі клацніть ім'я функції в секції стека викликів, і відбудеться переключення до подання Function Details (рис. 5).



Рис. 5. Подання Function Details


У цьому поданні ви бачите графічне відображення функцій, які викликали MatMult, а також функцій, що викликалися всередині неї. Нижня частина вистави чітко вказує, що за часту блокування потоків відповідальна EnterCriticalSection (& mmlock).


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


У додатку-прикладі використання в цьому коді даної критичної секції зайве, тому що потоки не здійснюють спільний запис в одні й ті самі рядки кінцевої матриці. Засоби аналізу продуктивності в Visual Studio підводять вас до точки, де ви можете закоментувати використання mmlock, що значно прискорить роботу додатка. Але якби це завжди було так просто!


Більш глибоке опис подання Function Details див. у блозі Visual Studio Profiler Team за посиланням


Thread Details


Як я вже згадував, уявлення Summary дає хорошу відправну точку для ваших досліджень. Вивчаючи таблиці Most Contended Resources і Most Contended Threads, ви можете вирішити, як діяти далі. Якщо ви виявите, що один з потоків веде себе досить підозріло, тому що ви не очікували, що він опиниться в списку потоків, що зазнають максимально інтенсивну конкуренцію, то можете уважніше подивитися на цей потік.


Клацніть ідентифікатор потоку в поданні Summary, щоб перейти до подання Thread Details (рис. 6). Хоча це подання схоже на Resource Details, у нього інший зміст – воно показує конкуренцію в контексті обраного потоку. Кожна горизонтальна лінія представляє ресурс, за який потік конкурував протягом свого життєвого циклу. На цій діаграмі ви не побачите перекриті за часом блоки конкуренції, тому що інакше це означало б, що потік одночасно блокується більш ніж на одному синхронизирующем ресурсі.



Рис. 6. Подання Thread Details з обраним блоком конкуренції


Зауважте, що WaitForMultipleObjects (яку я тут не показую) обробляється окремо і видається і представлена ​​єдиною рядком діаграми разом з набором своїх об'єктів. Це пов'язано з тим, що засіб профілювання інтерпретує всі об'єкти-параметри функції WaitForMultipleObjects як єдину сутність.


Будь-які маніпуляції, які можна виконувати в уявленні Resource Details (збільшення деталізації фрагментів діаграми, вибір конкретних блоків конкуренції та перегляд їх довжини в мілісекундах, а також аналіз стека викликів), підтримуються і в поданні Thread Details. Двічі клацніть ім'я функції в секції Contention Call Stack для переходу до подання Function Details цієї функції.


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


Локалізація причин проблеми


Ймовірно, ви помітили, що мітки осей діаграми є гіперпосиланнями. Це дозволяє перемикатися між деталізованими уявленнями ресурсів і потоків, щоразу встановлюючи потрібний контекст для подання; таке може знадобитися в итеративном процесі пошуку та усунення проблеми. Наприклад, ви виявили, що ресурс R1 блокує багато потоки. Тоді ви переключаєтеся з Resource Details в деталізоване уявлення потоку T1 і виявляєте, що він блокувався не тільки на R1, але іноді і на ресурсі R2. Після цього ви можете подивитися детальні відомості по R2 і проаналізувати всі потоки, які блокувалися ресурсом R2. Потім можна клацнути мітку зацікавив вас потоку T2 і перевірити всі ресурси, які його блокували, – і т. д.


Дані профілювання конкуренції не дадуть вам явної відповіді на питання, хто утримує блокування в будь-який обраний момент часу. Але враховуючи механізм "чесного" використання синхронізуючих об'єктів між потоками і ваше знання поведінки програми, ви можете ідентифікувати ймовірного власника блокування (потік, який успішно захопив синхронізує блокування), порівнюючи дані в Resource Details з даними в Thread Details і навпаки.


Наприклад, у поданні Thread Details ви бачите потік T, блокується на ресурсі R в момент t. Ви можете перемкнутися до подання Resource Details для R, клацнувши позначку R, і подивитися всі потоки, які блокувалися на R протягом життєвого циклу виконання програми. У момент t ви побачите деяку кількість потоків (у тому числі T), блокували на R. Потік, який не блокувався на R в момент t, і є ймовірний успішний власник блокування.


Раніше я зазначив, що лінія Total діаграми є проекцією всіх блоків конкуренції. Мітка Total також є гіперпосиланням, але з уявлення Resource Details вона перемикає вас до подання Contention (рис. 7), Яке представляє собою дерева викликів при конкуренції індивідуально для кожного ресурсу. "Гарячий шлях" (hot path) дерева викликів відповідного ресурсу активізується автоматично. Це уявлення відображає статистику по конкуренції і часу блокування для кожного ресурсу і для кожного вузла (функції) у дереві викликів для ресурсу. На відміну від інших уявлень це агрегує стеки конкуренції в дерево викликів для ресурсу (так само, як в інших режимах профілювання) і надає статистику для всього прогону програми.



Рис. 7. Подання Contention з "гарячим шляхом", застосованим до Critical Section 1


З уявлення Contention ви можете повернутися в Resource Details будь-якого ресурсу за допомогою контекстного меню. Вкажіть ресурс, клацніть його правою кнопкою миші і виберіть Show Contention Resource Details. У цьому контекстному меню доступні й інші цікаві операції. Так що рекомендую вивчити контекстні меню в уявленнях кошти профілювання – вони можуть виявитися досить корисними!


Клацніть мітку Total у поданні Thread Details, щоб відобразити подання Processes, де обраний потік (рис. 8). У цьому поданні ви можете побачити, коли потік був запущений щодо часу запуску програми, коли він був завершений, скільки часу виконувався, як часто він відчував конкуренцію з іншими потоками і скільки часу він блокувався за весь період конкуренції за ресурси (в мілісекундах і у відсотках від часу життєвого циклу потоку).



Рис. 8. Подання Processes


І знову можна повернутися до подання Thread Details для будь-якого потоку через контекстне меню: вкажіть який вас потік, клацніть його правою кнопкою миші і виберіть Show Thread Contention Details.


Інший можливий шлях аналізу – відображення подання Processes безпосередньо при відкритті файлу, сортування потоків клацанням заголовка одного з доступних стовпців (скажімо, сортування потоків за кількістю блоків конкуренції), вибір одного з потоків і перемикання на діаграму Contention Details для цього потоку – знову ж через контекстне меню.


Усунення проблеми і порівняння результатів


Знайшовши кореневу причину конкуренції за блокування в додатку, ви можете закоментувати критичну секцію mmlock, а потім повторно виконати профілювання:

Копіювати код

for (i = myid*PerProcessorChunk;
i < (myid+1)*PerProcessorChunk;
i++) {
// EnterCriticalSection(&mmlock);
for (j=0; j<SIZE; j++) {
for (k=0; k<SIZE; k++) {
C[i][j] += A[i][k]*B[k][j];
}
}
// LeaveCriticalSection(&mmlock);
}

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



Рис. 9. Подання Summary для результатів профілювання виправленого коду


Ми також можемо порівняти результати аналізу продуктивності нової і попередньої версій в Visual Studio. Для цього вкажіть обидва файли в Performance Explorer (виберіть один файл, натисніть Shift або Ctrl, а виберіть другий), потім клацніть їх правою кнопкою миші і виберіть Compare Performance Reports.


З'явиться Comparison Report, як показано на рис. 10. У додатку-прикладі ви побачите, що кількість Inclusive Contentions для функції MatMult впало з 1003 до 0.



Рис. 10. Вікно Comparison Report


Альтернативні методи збору даних


Якщо ви створюєте сеанс аналізу продуктивності для профілювання в режимі Sampling (вибірка) або Instrumentation (оснащення засобами моніторингу та протоколювання), ви завжди можете згодом перевести його в режим Concurrency. Один із способів швидко зробити це – використовувати меню режимів профілювання в Performance Explorer. Просто виберіть потрібний режим, і все.


Ви також можете скористатися налаштуванням властивостей свого сеансу. Вкажіть цей сеанс в Performance Explorer, клацніть правою кнопкою миші, щоб відкрити контекстне меню, і виберіть Properties. На вкладці General сторінок властивостей можна контролювати режим сеансу профілювання та інші параметри.


Як тільки режим профілювання заданий Concurrency (або Sampling, якщо це має значення), ви можете або запустити свій додаток (воно вже знаходиться в списку Targets, якщо ви користувалися майстром Performance Wizard; крім того, туди його можна додати вручну), або підключитися до додатка, яке вже запущено і виконується. Performance Explorer дозволяє управляти цими операціями, як показано на рис. 11.



Рис. 11. Засоби Performance Explorer для управління профілюванням


Visual Studio UI автоматизує ряд операцій, необхідних для збору даних профілювання. Проте ці дані можна збирати засобами командного рядка, що може бути корисно в автоматизованих прогонах і сценаріях.


Щоб запустити програму в режимі профілювання конкуренції, відкрийте вікно командного рядка в Visual Studio (що поміщає в ваш шлях все виконавчі файли кошти профілювання для платформи x86 або x64), а потім виконайте наступне.



  1. Введіть VSPerfCmd.exe / start: CONCURRENCY, RESOURCEONLY / output: <Ваш вихідний файл>.

  2. Введіть VSPerfCmd.exe / launch: <ваше застосування> / args: "<аргументи вашого застосування>".

  3. Запустіть сценарій.

  4. Введіть VSPerfCmd.exe / detach.

    • Ця операція не обов'язкове, якщо ваш додаток завершується, але й нічим не зашкодить, тому краще додайте її в свої сценарії.

  5. Введіть VSPerfCmd.exe / shutdown.

    Тепер ви можете відкрити Ваш_виходной_файл.VSP в Visual Studio для аналізу.


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



  1. Введіть VSPerfCmd.exe / start: CONCURRENCY, RESOURCEONLY / output: <Ваш вихідний файл>.

  2. Введіть VSPerfCmd.exe / attach: <PID або ім'я процесса>.

  3. Запустіть сценарій.

  4. Введіть VSPerfCmd.exe / detach.

  5. Введіть VSPerfCmd.exe / shutdown.

Більш детальне пояснення доступних параметрів командного рядка див за посиланням msdn.microsoft.com/library/bb385768 (VS.100).


Різноманітність уявлень Visual Studio дозволяє ретельно аналізувати зібрані дані під різними кутами зору. Деякі уявлення дають картину життєвого циклу програми в цілому, а інші – Більш специфічну інформацію, і ви можете використовувати ті, що вважаєте максимально корисними.


При аналізі результатів профілювання ви можете перемикатися між уявленнями через гіперпосилання, подвійними клацаннями або за допомогою контекстних меню, а також переходячи безпосередньо до потрібного поданням у спадному меню. В табл. 1 коротко описується кожна з цих уявлень.


Рис. 12. Аналітичні подання














































Подання Опис
Summary Надає зведену інформацію, яка служить відправною точкою у ваших дослідженнях. Це перша вистава, що ви бачите, і воно відкривається автоматично після закінчення сеансу профілювання і після готовності файлу результатів
Call Tree Дерево викликів, агрегують все стеки викликів, де спостерігається конкуренція. Тут ви можете зрозуміти, які стеки відповідальні за конкуренцію
Modules Список модулів, що містять функції, кожна з яких призводить до конкуренції. Для кожного модуля створюється список релевантних функцій і повідомляється число виявлених блоків конкуренції
Caller/Callee Подання з трьома секціями, де відображаються функція F, всі функції, що викликають F, і функції, що викликаються F (показуються, звичайно, тільки виклики, що призводять до конкуренції)
Опції Список всіх функцій, виявлених в будь-якому стеку, де є конкуренція, з зіставленими даними
Lines Рядки функції у вихідному коді
Resource Details Детальні відомості про конкретний ресурс (наприклад, про блокування), де показуються всі потоки, що блокували на цьому ресурсі в ході життєвого циклу програми
Thread Details Детальні відомості про конкретний потоці з відображенням усіх ресурсів (таких як блокувань), на яких блокувався даний потік
Contention Аналогічно поданням Call Tree, але тут дерева викликів розділені по ресурсу, за який конкурують потоки. Інакше кажучи, воно представляє набір дерев викликів, кожне з яких містить стеки, блокуватися на конкретному ресурсі
Marks Список автоматично і вручну записаних міток (marks), де кожна мітка зіставлена ​​зі своєї тимчасової міткою і значеннями Windows-лічильників
Processes Список перевірялися процесів, де кожен процес має список своїх потоків; при цьому кожному потоку зіставлені кількість випадків конкуренції і сумарна тривалість його блокування
Function Details Детальні відомості про конкретну функцію, у тому числі про викликаються нею функціях, і зібрані дані
IPs Список покажчиків команд (IP), де відбувалася конкуренція (фактично це список функцій на зразок EnterCriticalSection, WaitForSingleObject та ін, так як реально конкуренція спостерігається на таких функціях)

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

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*