Измерьте прошедшее время в Swift

132

Как мы можем измерить время, затраченное на выполнение функции в Swift? Я пытаюсь отобразить прошедшее время следующим образом: «Прошедшее время составляет 0,05 секунды». Видел, что в Java мы можем использовать System.nanoTime (), есть ли в Swift какие-либо эквивалентные методы для этого?

Взгляните на пример программы:

func isPrime(var number:Int) ->Bool {
    var i = 0;
    for i=2; i<number; i++ {
        if(number % i == 0 && i != 0) {
            return false;
        }
    }
    return true;
}

var number = 5915587277;

if(isPrime(number)) {
    println("Prime number");
} else {
    println("NOT a prime number");
}
Vaquita
источник
1
это не связано с вашей проблемой измерения времени, но sqrt(number)вместо этого цикл можно остановить number, и вы можете сэкономить немного больше времени, но есть гораздо больше идей по оптимизации поиска простых чисел.
holex
@holex Не обращайте внимания на используемый алгоритм. Я пытаюсь понять, как мы можем измерить прошедшее время?
Vaquita
1
вы можете использовать NSDateпредметы и вы можете измерить разницу между ними.
holex
4
Если вы используете XCode, я рекомендую вам использовать новую функцию тестирования производительности. Он делает всю тяжелую работу за вас и даже запускает его несколько раз и дает вам среднее время со стандартным отклонением ...
Рошан
1
@dumbledad Вы можете измерить производительность целых блоков непосредственно в модульных тестах. Например, посмотрите это . Если вы хотите разбить анализ дальше (например, построчно), посмотрите Time Profiler, который является частью Instruments. Это гораздо более мощный и всеобъемлющий подход.
Рошан

Ответы:

229

Вот функция Swift, которую я написал для измерения проблем Project Euler в Swift.

Начиная с Swift 3, теперь есть версия Grand Central Dispatch, которая является «быстрой». Так что правильным ответом, вероятно, будет использование DispatchTime API .

Моя функция будет выглядеть примерно так:

// Swift 3
func evaluateProblem(problemNumber: Int, problemBlock: () -> Int) -> Answer
{
    print("Evaluating problem \(problemNumber)")

    let start = DispatchTime.now() // <<<<<<<<<< Start time
    let myGuess = problemBlock()
    let end = DispatchTime.now()   // <<<<<<<<<<   end time

    let theAnswer = self.checkAnswer(answerNum: "\(problemNumber)", guess: myGuess)

    let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds // <<<<< Difference in nano seconds (UInt64)
    let timeInterval = Double(nanoTime) / 1_000_000_000 // Technically could overflow for long running tests

    print("Time to evaluate problem \(problemNumber): \(timeInterval) seconds")
    return theAnswer
}

Старый ответ

Для Swift 1 и 2 моя функция использует NSDate:

// Swift 1
func evaluateProblem(problemNumber: Int, problemBlock: () -> Int) -> Answer
{
    println("Evaluating problem \(problemNumber)")

    let start = NSDate() // <<<<<<<<<< Start time
    let myGuess = problemBlock()
    let end = NSDate()   // <<<<<<<<<<   end time

    let theAnswer = self.checkAnswer(answerNum: "\(problemNumber)", guess: myGuess)

    let timeInterval: Double = end.timeIntervalSinceDate(start) // <<<<< Difference in seconds (double)

    println("Time to evaluate problem \(problemNumber): \(timeInterval) seconds")
    return theAnswer
}

Обратите внимание, что использование NSdate для функций синхронизации не рекомендуется: « Системное время может уменьшиться из-за синхронизации с внешними привязками времени или из-за явной смены часов пользователем ».

JeremyP
источник
Это дает хорошие результаты с точностью до +/- 2 мкс. Однако, возможно, это зависит от платформы.
user1021430
6
swift3 не работает для меня, он возвращает 0,0114653027, который, как я знаю, неверен, тот, у которого есть даты, возвращает 4,77720803022385сек
Cristi Băluță
5
К сожалению нет. По какой-то причине время безотказной работы полностью отсутствует. У меня есть тест, который занял 1004959766 наносекунд (около секунды), но определенно работал около 40 секунд. Я использовал Dateрядом, и, конечно же, он сообщает 41,87 секунды. Я не знаю, что uptimeNanosecondsпроисходит, но не сообщает правильную продолжительность.
jowie 05
2
Приносим извинения за большое изменение, отдавая приоритет вашему новому решению, но я бы даже рекомендовал полностью удалить ваш код NSDate: использовать NSDate совершенно ненадежно: NSDate основан на системных часах, которые могут измениться в любое время по разным причинам, например как синхронизация времени по сети (NTP), обновляющая часы (часто бывает, чтобы отрегулировать дрейф), настройки летнего времени, дополнительных секунд и ручную настройку часов пользователем. Кроме того, вы больше не можете публиковать в AppStore какое-либо приложение, использующее Swift 1: Swift 3 - это минимум. Таким образом, нет смысла сохранять свой код NSDate, кроме как сказать «не делай этого».
Cœur
1
@ Cœur ваша критика версии NSDate действительна (за исключением того, что изменения DST не повлияют на NSDate, который всегда находится в GMT), и ваше изменение для изменения порядка, безусловно, является улучшением.
JeremyP
69

Это удобный класс таймера, основанный на CoreFoundations CFAbsoluteTime:

import CoreFoundation

class ParkBenchTimer {

    let startTime:CFAbsoluteTime
    var endTime:CFAbsoluteTime?

    init() {
        startTime = CFAbsoluteTimeGetCurrent()
    }

    func stop() -> CFAbsoluteTime {
        endTime = CFAbsoluteTimeGetCurrent()

        return duration!
    }

    var duration:CFAbsoluteTime? {
        if let endTime = endTime {
            return endTime - startTime
        } else {
            return nil
        }
    }
}

Вы можете использовать это так:

let timer = ParkBenchTimer()

// ... a long runnig task ...

println("The task took \(timer.stop()) seconds.")
Клаас
источник
4
« Повторные вызовы этой функции не гарантируют монотонно возрастающих результатов », согласно его документации .
Франклин Ю
8
Дополнительный контекст: «Повторные вызовы этой функции не гарантируют монотонно возрастающих результатов. Системное время может уменьшаться из-за синхронизации с внешними привязками времени или из-за явной смены часов пользователем ».
Клаас
Разработчик должен принять во внимание «синхронизацию с внешними привязками времени» и «явное изменение часов пользователем». Вы имели в виду, что две ситуации не могут произойти?
Франклин Ю
@FranklinYu нет, я просто хотел указать, что это две возможные причины. Если вы рассчитываете всегда получать монотонно возрастающие значения, есть другие варианты.
Клаас
иногда я хочу измерить короткий промежуток времени; если при этом произойдет синхронизация, я могу получить отрицательное время. На самом деле существует тот, mach_absolute_timeкоторый не страдает этой проблемой, поэтому мне интересно, почему он не так широко известен. Может быть, просто потому, что это плохо документировано.
Франклин Ю
54

Используйте clock, ProcessInfo.systemUptimeили DispatchTimeдля простого времени запуска.


Насколько мне известно, существует как минимум десять способов измерить прошедшее время:

Монотонные часы на основе:

  1. ProcessInfo.systemUptime,
  2. mach_absolute_timeс , mach_timebase_infoкак указано в этом ответе .
  3. clock()в стандарте POSIX .
  4. times()в стандарте POSIX . (Слишком сложно, поскольку нам нужно учитывать время пользователя и время системы, а также задействованы дочерние процессы.)
  5. DispatchTime (оболочка вокруг API времени Маха), как упоминалось JeremyP в принятом ответе.
  6. CACurrentMediaTime(),

Настенные часы на основе:

(никогда не используйте их для показателей: см. ниже, почему)

  1. NSDate/ Dateкак упоминалось другими.
  2. CFAbsoluteTime как упоминалось другими.
  3. DispatchWallTime,
  4. gettimeofday()в стандарте POSIX .

Варианты 1, 2 и 3 подробно описаны ниже.

Вариант 1. API информации о процессе в Foundation

do {
    let info = ProcessInfo.processInfo
    let begin = info.systemUptime
    // do something
    let diff = (info.systemUptime - begin)
}

где diff:NSTimeInterval- прошедшее время в секундах.

Вариант 2: Mach C API

do {
    var info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&info)
    let begin = mach_absolute_time()
    // do something
    let diff = Double(mach_absolute_time() - begin) * Double(info.numer) / Double(info.denom)
}

где diff:Double- прошедшее время в наносекундах.

Вариант 3: API часов POSIX

do {
    let begin = clock()
    // do something
    let diff = Double(clock() - begin) / Double(CLOCKS_PER_SEC)
}

где diff:Double- прошедшее время в секундах.

Почему настенные часы не показывают истекшее время?

В документации CFAbsoluteTimeGetCurrent:

Повторные вызовы этой функции не гарантируют монотонно возрастающих результатов.

Причина аналогична currentTimeMillisvs nanoTimeв Java :

Вы не можете использовать одно для другой цели. Причина в том, что никакие компьютерные часы не идеальны; он всегда дрейфует и иногда требует исправления. Это исправление может происходить либо вручную, либо, в случае большинства машин, есть процесс, который выполняется и постоянно вносит небольшие исправления в системные часы («настенные часы»). Это часто случается. Еще одна такая коррекция происходит всякий раз, когда есть дополнительная секунда.

Здесь CFAbsoluteTimeуказано время настенных часов вместо времени запуска. NSDateвремя настенных часов.

Франклин Ю
источник
лучший ответ - но какой, черт возьми, лучший подход ?!
Fattie
1
Вариант 5 мне больше всего подошел. Если вы рассматриваете многоплатформенный код, попробуйте этот. Обе библиотеки Darwin и Glibc имеют clock()функцию.
Миша
Для решений на основе системного времени: я имел успех ProcessInfo.processInfo.systemUptime, mach_absolute_time(), DispatchTime.now()и CACurrentMediaTime(). Но решение с clock()некорректным преобразованием в секунды. Поэтому я бы посоветовал обновить ваше первое предложение следующим образом: « Используйте ProcessInfo.systemUptime или DispatchTime для точного времени запуска ».
Cœur
36

Swift 4 самый короткий ответ:

let startingPoint = Date()
//  ... intensive task
print("\(startingPoint.timeIntervalSinceNow * -1) seconds elapsed")

Он напечатает вам что-то вроде прошедшего времени 1.02107906341553 секунды (время, конечно, будет варьироваться в зависимости от задачи, я просто показываю это вам, ребята, чтобы увидеть уровень десятичной точности для этого измерения).

Надеюсь, что с этого момента это поможет кому-то в Swift 4!

Обновить

Если вы хотите иметь общий способ тестирования частей кода, я бы предложил следующий фрагмент:

func measureTime(for closure: @autoclosure () -> Any) {
    let start = CFAbsoluteTimeGetCurrent()
    closure()
    let diff = CFAbsoluteTimeGetCurrent() - start
    print("Took \(diff) seconds")
}

*Usage*

measureTime(for: <insert method signature here>)

**Console log**
Took xx.xxxxx seconds
Маурисио Чирино
источник
Может кто-нибудь объяснить, почему * -1?
Максим Князев
1
@MaksimKniazev Потому что это отрицательное значение, люди хотят положительного!
thachnb
@thachnb Я понятия не имел, что "startPoint.timeIntervalSinceNow" дает отрицательное значение ...
Максим Князев
1
@MaksimKniazev Apple говорит, что: Если объект даты раньше, чем текущая дата и время, значение этого свойства отрицательное.
Peter Guan
13
let start = NSDate()

for index in 1...10000 {
    // do nothing
}

let elapsed = start.timeIntervalSinceNow

// elapsed is a negative value.
user3370455
источник
2
abs (start.timeIntervalSinceNow) // <- положительное значение
eonist
в значительной степени самое простое решение, которое я видел, спасибо
MiladiuM
10

Просто скопируйте и вставьте эту функцию. Написано быстрым 5. Копируем JeremyP сюда.

func calculateTime(block : (() -> Void)) {
        let start = DispatchTime.now()
        block()
        let end = DispatchTime.now()
        let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
        let timeInterval = Double(nanoTime) / 1_000_000_000
        print("Time: \(timeInterval) seconds")
    }

Используйте это как

calculateTime {
     exampleFunc()// function whose execution time to be calculated
}
ShaileshAher
источник
6

Вы можете создать timeфункцию для измерения ваших звонков. Меня вдохновил ответ Клааса .

func time <A> (f: @autoclosure () -> A) -> (result:A, duration: String) {
    let startTime = CFAbsoluteTimeGetCurrent()
    let result = f()
    let endTime = CFAbsoluteTimeGetCurrent()
    return (result, "Elapsed time is \(endTime - startTime) seconds.")
}

Эта функция позволит вам вызвать ее так, time (isPrime(7))что вернет кортеж, содержащий результат и строковое описание прошедшего времени.

Если вы хотите только прошедшее время, вы можете сделать это time (isPrime(7)).duration

Mellson
источник
3
«Повторные вызовы этой функции не гарантируют монотонно возрастающих результатов». согласно документации .
Франклин Ю
3

Простая вспомогательная функция для измерения времени выполнения с закрытием.

func printExecutionTime(withTag tag: String, of closure: () -> ()) {
    let start = CACurrentMediaTime()
    closure()
    print("#\(tag) - execution took \(CACurrentMediaTime() - start) seconds")
}

Использование:

printExecutionTime(withTag: "Init") {
    // Do your work here
}

Результат: #Init - execution took 1.00104497105349 seconds

Вадим Ахмеров
источник
2

вы можете измерить наносекунды, например, так:

let startDate: NSDate = NSDate()

// your long procedure

let endDate: NSDate = NSDate()
let dateComponents: NSDateComponents = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian).components(NSCalendarUnit.CalendarUnitNanosecond, fromDate: startDate, toDate: endDate, options: NSCalendarOptions(0))
println("runtime is nanosecs : \(dateComponents.nanosecond)")
holex
источник
Я просто запустил это без кода, помещенного в "// ваша длинная процедура", и он дал результат 240900993, что составляет 0,24 секунды.
user1021430
создание экземпляра - NSCalendarэто дорогостоящая процедура, но вы можете сделать это до того, как начнете запускать свою процедуру, поэтому она не будет добавлена ​​в вашу длинную среду выполнения ... имейте в виду, что вызов создания NSDateэкземпляра и вызова –components(_:, fromDate:)метода все еще требует времени, поэтому, вероятно, вы никогда не получите 0.0наносекунды.
holex
По внешнему виду конструктора NSCalendar кажется, что создать его заранее невозможно (startDate является обязательным вводом). Удивительно, что он такой боров.
user1021430
вы можете сделать это (мой обновленный ответ), измеренное время без какой-либо дополнительной процедуры составляет около 6.9микросекунд на моем устройстве.
holex
Хорошо, спасибо. Основная процедура теперь аналогична ответу JeremyP, но отчет отличается.
user1021430
2

Я использую это:

public class Stopwatch {
    public init() { }
    private var start_: NSTimeInterval = 0.0;
    private var end_: NSTimeInterval = 0.0;

    public func start() {
        start_ = NSDate().timeIntervalSince1970;
    }

    public func stop() {
        end_ = NSDate().timeIntervalSince1970;
    }

    public func durationSeconds() -> NSTimeInterval {
        return end_ - start_;
    }
}

Я не знаю, более или менее точно, чем было опубликовано ранее. Но секунды имеют много десятичных знаков и, кажется, улавливают небольшие изменения кода в алгоритмах, таких как QuickSort, с использованием swap () по сравнению с реализацией urself подкачки и т. Д.

Не забудьте активизировать оптимизацию сборки при тестировании производительности:

Оптимизация компилятора Swift

Peheje
источник
2

Вот моя попытка найти самый простой ответ:

let startTime = Date().timeIntervalSince1970  // 1512538946.5705 seconds

// time passes (about 10 seconds)

let endTime = Date().timeIntervalSince1970    // 1512538956.57195 seconds
let elapsedTime = endTime - startTime         // 10.0014500617981 seconds

Ноты

  • startTimeи endTimeотносятся к типу TimeInterval, который является просто typealiasfor Double, поэтому его легко преобразовать в Intили что-то еще. Время измеряется в секундах с точностью до миллисекунды.
  • См. Также DateInterval, который включает фактическое время начала и окончания.
  • Использование времени с 1970 года аналогично временным меткам Java .
Suragch
источник
Самый
FreeGor
1

Я позаимствовал идею Клааса, чтобы создать легкую структуру для измерения времени бега и интервалов:

Использование кода:

var timer = RunningTimer.init()
// Code to be timed
print("Running: \(timer) ") // Gives time interval
// Second code to be timed
print("Running: \(timer) ") // Gives final time

Функцию остановки вызывать не нужно, так как функция печати покажет истекшее время. Его можно вызывать повторно, чтобы отсчитать время. Но для остановки таймера в определенный момент использования кода timer.stop()его также можно использовать для возврата времени в секундах: let seconds = timer.stop() после остановки таймера интервальный таймер не будет, поэтому он print("Running: \(timer) ")будет давать правильное время даже после нескольких строк кода.

Ниже приведен код для RunningTimer. Протестировано для Swift 2.1:

import CoreFoundation
// Usage:    var timer = RunningTimer.init()
// Start:    timer.start() to restart the timer
// Stop:     timer.stop() returns the time and stops the timer
// Duration: timer.duration returns the time
// May also be used with print(" \(timer) ")

struct RunningTimer: CustomStringConvertible {
    var begin:CFAbsoluteTime
    var end:CFAbsoluteTime

    init() {
        begin = CFAbsoluteTimeGetCurrent()
        end = 0
    }
    mutating func start() {
        begin = CFAbsoluteTimeGetCurrent()
        end = 0
    }
    mutating func stop() -> Double {
        if (end == 0) { end = CFAbsoluteTimeGetCurrent() }
        return Double(end - begin)
    }
    var duration:CFAbsoluteTime {
        get {
            if (end == 0) { return CFAbsoluteTimeGetCurrent() - begin } 
            else { return end - begin }
        }
    }
    var description:String {
    let time = duration
    if (time > 100) {return " \(time/60) min"}
    else if (time < 1e-6) {return " \(time*1e9) ns"}
    else if (time < 1e-3) {return " \(time*1e6) µs"}
    else if (time < 1) {return " \(time*1000) ms"}
    else {return " \(time) s"}
    }
}
Сверриссон
источник
1

Оберните его в блок завершения для удобства использования.

public class func secElapsed(completion: () -> Void) {
    let startDate: NSDate = NSDate()
    completion()
    let endDate: NSDate = NSDate()
    let timeInterval: Double = endDate.timeIntervalSinceDate(startDate)
    println("seconds: \(timeInterval)")
}
RiceAndBytes
источник
0

Это фрагмент, который я придумал, и, похоже, он работает для меня на моем Macbook с Swift 4.

Никогда не тестировал на других системах, но подумал, что все равно стоит поделиться.

typealias MonotonicTS = UInt64
let monotonic_now: () -> MonotonicTS = mach_absolute_time

let time_numer: UInt64
let time_denom: UInt64
do {
    var time_info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&time_info)
    time_numer = UInt64(time_info.numer)
    time_denom = UInt64(time_info.denom)
}

// returns time interval in seconds
func monotonic_diff(from: MonotonicTS, to: MonotonicTS) -> TimeInterval {
    let diff = (to - from)
    let nanos = Double(diff * time_numer / time_denom)
    return nanos / 1_000_000_000
}

func seconds_elapsed(since: MonotonicTS) -> TimeInterval {
    return monotonic_diff(from: since, to:monotonic_now())
}

Вот пример того, как его использовать:

let t1 = monotonic_now()
// .. some code to run ..
let elapsed = seconds_elapsed(since: t1)
print("Time elapsed: \(elapsed*1000)ms")

Другой способ - сделать это более явно:

let t1 = monotonic_now()
// .. some code to run ..
let t2 = monotonic_now()
let elapsed = monotonic_diff(from: t1, to: t2)
print("Time elapsed: \(elapsed*1000)ms")
Hasen
источник
0

Вот как я это написал.

 func measure<T>(task: () -> T) -> Double {
        let startTime = CFAbsoluteTimeGetCurrent()
        task()
        let endTime = CFAbsoluteTimeGetCurrent()
        let result = endTime - startTime
        return result
    }

Чтобы измерить алгоритм, используйте его вот так.

let time = measure {
    var array = [2,4,5,2,5,7,3,123,213,12]
    array.sorted()
}

print("Block is running \(time) seconds.")
BilalReffas
источник
Насколько точно это время? Потому что раньше я использовал таймер для обновления каждые 0,1 секунды. Я сравнил этот и один ответ с таймером, и результат этого ответа на 0,1 секунды больше, чем с таймером ..
Максим Князев
0

Статический класс Swift3 для синхронизации основных функций. Он будет отслеживать каждый таймер по имени. Назовите это так в точке, с которой хотите начать измерения:

Stopwatch.start(name: "PhotoCapture")

Вызовите это, чтобы зафиксировать и распечатать прошедшее время:

Stopwatch.timeElapsed(name: "PhotoCapture")

Это результат: *** PhotoCapture elapsed ms: 1402.415125 Есть параметр «useNanos», если вы хотите использовать нано. Пожалуйста, не стесняйтесь вносить изменения по мере необходимости.

   class Stopwatch: NSObject {

  private static var watches = [String:TimeInterval]()

  private static func intervalFromMachTime(time: TimeInterval, useNanos: Bool) -> TimeInterval {
     var info = mach_timebase_info()
     guard mach_timebase_info(&info) == KERN_SUCCESS else { return -1 }
     let currentTime = mach_absolute_time()
     let nanos = currentTime * UInt64(info.numer) / UInt64(info.denom)
     if useNanos {
        return (TimeInterval(nanos) - time)
     }
     else {
        return (TimeInterval(nanos) - time) / TimeInterval(NSEC_PER_MSEC)
     }
  }

  static func start(name: String) {
     var info = mach_timebase_info()
     guard mach_timebase_info(&info) == KERN_SUCCESS else { return }
     let currentTime = mach_absolute_time()
     let nanos = currentTime * UInt64(info.numer) / UInt64(info.denom)
     watches[name] = TimeInterval(nanos)
  }

  static func timeElapsed(name: String) {
     return timeElapsed(name: name, useNanos: false)
  }

  static func timeElapsed(name: String, useNanos: Bool) {
     if let start = watches[name] {
        let unit = useNanos ? "nanos" : "ms"
        print("*** \(name) elapsed \(unit): \(intervalFromMachTime(time: start, useNanos: useNanos))")
     }
  }

}

Gjchoza
источник
Вся ваша работа с mach_timebase_infoуже реализована лучше , чем вы делали в исходном коде из ProcessInfo.processInfo.systemUptime. Так что просто делай watches[name] = ProcessInfo.processInfo.systemUptime. А * TimeInterval(NSEC_PER_MSEC)если хотите нан.
Cœur
0

На основе ответа Франклина Ю и комментариев Кёра

подробности

  • Xcode 10.1 (10B61)
  • Swift 4.2

Решение 1

измерения (_ :)

Решение 2

import Foundation

class Measurer<T: Numeric> {

    private let startClosure: ()->(T)
    private let endClosure: (_ beginningTime: T)->(T)

    init (startClosure: @escaping ()->(T), endClosure: @escaping (_ beginningTime: T)->(T)) {
        self.startClosure = startClosure
        self.endClosure = endClosure
    }

    init (getCurrentTimeClosure: @escaping ()->(T)) {
        startClosure = getCurrentTimeClosure
        endClosure = { beginningTime in
            return getCurrentTimeClosure() - beginningTime
        }
    }

    func measure(closure: ()->()) -> T {
        let value = startClosure()
        closure()
        return endClosure(value)
    }
}

Использование раствора 2

// Sample with ProcessInfo class

m = Measurer { ProcessInfo.processInfo.systemUptime }
time = m.measure {
    _ = (1...1000).map{_ in Int(arc4random()%100)}
}
print("ProcessInfo: \(time)")

// Sample with Posix clock API

m = Measurer(startClosure: {Double(clock())}) { (Double(clock()) - $0 ) / Double(CLOCKS_PER_SEC) }
time = m.measure {
    _ = (1...1000).map{_ in Int(arc4random()%100)}
}
print("POSIX: \(time)")
Василий Боднарчук
источник
Не уверен, зачем нам нужно использовать реализации переменных. Использование Dateдля измерения ничего крайне не рекомендуется (но systemUptimeили clock()ОК). Что касается тестов, то мы их measure(_:)уже сделали .
Cœur
1
Еще одно замечание: почему закрытие необязательно?
Cœur
@ Cœur ты прав! Спасибо за ваши комментарии. Я обновлю пост позже.
Василий Боднарчук