Виміряйте минулий час у Свіфті


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");
}

1
це не пов'язане з вашим питанням вимірювання часу, але цикл можна зупинити на sqrt(number)замість цього number, і ви можете заощадити трохи більше часу - але є набагато більше ідей, що оптимізують пошук праймерів.
holex

@holex Будь ласка, ігноруйте використаний алгоритм. Я намагаюся розібратися, як ми можемо виміряти минулий час?
Вакіта

1
ви можете використовувати NSDateоб'єкти, і ви можете виміряти різницю між ними.
holex

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

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

Відповіді:


229

Ось функція Swift, яку я написав для вимірювання проблем Project Euler у Swift

Станом на Swift 3, тепер існує версія Grand Central Dispatch, яка є "перекрученою". Тому правильна відповідь, ймовірно, використовувати API DispatchTime .

Моя функція виглядала б приблизно так:

// 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 для функцій синхронізації не рекомендується: " Системний час може скорочуватися через синхронізацію із зовнішніми посиланнями часу або через явну зміну годинника користувачем. "


Здається, це дає хороші результати приблизно до +/- 2microsec. Можливо, це залежить від платформи.
user1021430

6
swift3 не працює для мене, він повертає 0,0114653027, що я знаю за фактом, який не відповідає дійсності, той, з датами повертається 4.77720803022385сек.
Крісті Балуша

5
На жаль ні. Час безперервної роботи повністю вичерпаний. У мене є тест, який говорить, що зайняв 1004959766 наносекунд (близько секунди), але, безумовно, працював близько 40 секунд. Я використовував Dateпоряд, і досить впевнений, що повідомляє 41,87 секунди. Я не знаю, що uptimeNanosecondsробить, але це не повідомляє про правильну тривалість.
jowie

2
Вибачте за велику редагування, визначивши пріоритет з вашим новим рішенням, але я б навіть рекомендував повністю видалити ваш код NSDate: використовувати NSDate: NSDate засновано на системному годиннику, який може змінюватися в будь-який час через багато різних причин, наприклад як мережева синхронізація часу (NTP), що оновлює годинник (трапляється часто для налаштування дрейфу), налаштування DST, стрибкові секунди та коригування годинника вручну. Крім того, ви більше не можете публікувати в 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 часу Mach), як згадував 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: API Mach C

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 мені найбільше підходить. Якщо ви розглядаєте багатоплатформенний код, спробуйте цей. clock()Функціонують і бібліотеки Дарвіна, і Glibc .
Міша Святошенко

Для рішень на основі системного часу: я мав успіх ProcessInfo.processInfo.systemUptime, mach_absolute_time(), DispatchTime.now()і CACurrentMediaTime(). Але рішення з clock()не було перетворення на секунди належним чином. Тому я б радив оновити ваше перше речення на " Використовуйте ProcessInfo.systemUptime або DispatchTime для точного часу запуску ".
Cœur

36

Швидкий 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 каже, що: Якщо об’єкт дати є попереднім поточним датою та часом, значення цього властивості є негативним.
Пітер Гуан


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
}

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


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)")

Я просто запустив це без коду, розміщеного у "// вашій довгій процедурі", і це дало результат 240900993, що становить 0,24 секунди.
користувач1021430

інстанціювання NSCalendarдорогої процедури, але ви можете це зробити до того, як ви почнете запускати процедуру, тому вона не буде додана до тривалої процедури вашої процедури ... майте на увазі, виклик створення NSDateекземпляра та –components(_:, fromDate:)методу виклику все ще потребує часу, тому, ймовірно, ви ніколи не отримаєте 0.0наносекунд.
holex

З вигляду конструктора NSCalendar випливає, що його неможливо створити достроково (startDate - це необхідний вклад). Це щось дивно, що, здається, це така свиня.
користувач1021430

Ви можете зробити це (моя оновлена ​​відповідь), вимірюваний час без будь-якої додаткової процедури - приблизно 6.9мікросекунди на моєму пристрої.
holex

Класно, дякую. Зараз основна процедура схожа на відповідь ДжереміП, але звітність відрізняється.
користувач1021430

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 () порівняно з реалізацією urp swap тощо.

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

Швидкі оптимізації компілятора


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, який є лише typealiasдля Double, тому легко перетворити його наInt чи інше. Час вимірюється в секундах з субмілісекундною точністю.
  • Дивитися також DateInterval , що включає фактичний час початку та закінчення.
  • Використання часу з 1970 року схоже на часові позначки Java .

Найпростіше, хотілося б, щоб це було в двох рядках, другий - дозволено минув час = дата (). часIntervalSince1970 - startTime
FreeGor

1

Я взяв у Klaas ідею створити легку структуру для вимірювання часу та інтервалу:

Використання коду:

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)")
}

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")

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.")

Наскільки точний цей час? Тому що я раніше використовував таймер, щоб оновлювати кожні 0,1 секунди. Я порівняв цю відповідь та один із таймером, і результат цієї відповіді на 0,1 секунди більше, ніж із таймером ..
Максим Князєв,

0

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

Stopwatch.start(name: "PhotoCapture")

Зателефонуйте цьому, щоб зафіксувати та надрукувати минулий час:

Stopwatch.timeElapsed(name: "PhotoCapture")

Це вихід: *** PhotoCapture минуло ms: 1402.415125 Існує параметр "useNanos", якщо ви хочете використовувати nanos. Будь ласка, не соромтесь змінювати, якщо потрібно.

   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))")
     }
  }

}


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

0

На основі відповіді Франкліна Ю. та коментарів Cœur

Деталі

  • Xcode 10.1 (10B61)
  • Швидкий 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 ви праві! Дякуємо за ваші коментарі. Я оновлю публікацію пізніше.
Василь Боднарчук
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.