Повышение эффективности хранения миллионов экземпляров структур 2-UUID на диске за короткое время

В моем приложении мне нужно хранить на диске миллионы пользовательских структур, содержащих 2 атрибута UUID. Мой проект требует, чтобы они были записаны и прочитаны на диск очень быстро. Я попробовал использовать JSONDecoder, но обнаружил, что производительность очень низкая при сохранении от 100 тысяч до примерно 10 миллионов этих структур, что заняло от 3 до 30 секунд, как показало тщательное тестирование.

Есть ли гораздо более эффективный способ сохранить структуру всего с двумя UUID, например, используя двоичное представление? Мне не удалось найти ресурсы по сохранению UUID на диск. Мне не нужен формат JSON для сохранения и чтения с диска.

struct MetadataItem: Identifiable, Codable {
    var id = UUID()
    let itemId: UUID
}

//Just to simulate adding the custom structs
for i in 1...1000000 {
     let newItem = MetadataItem(itemId: UUID())
     metadataItems.append(newItem)
}
                            
do {
    let data = try JSONEncoder().encode(metadataItems)
    if let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
        try data.write(to: url.appending(path: "Metadata"))
    }
    
    print("Done")
} catch {

}

CoreData? Проблема с массивом JSON заключается в том, что вам нужно читать и записывать все значения одновременно, поэтому для изменения или добавления одного значения вам нужно прочитать весь массив, а затем переписать его. Каждый раз, когда вы имеете дело с IO и 10 миллионами, это может занять некоторое время. Вам необходимо предоставить более подробную информацию. Это разовая операция? Нужна ли вам возможность поиска по этим данным? Данные обновляются, удаляются или добавляются? Как часто это происходит?

Paulw11 14.07.2024 00:25

Я пробовал основные данные, но сохранение большого количества этих данных было слишком медленным, в среднем около 5 секунд или намного больше даже для 100 тысяч этих записей. Мое приложение требует, чтобы данные были сохранены и прочитаны как можно быстрее как минимум для миллиона этих структур UUID. Мне нужно просмотреть массив uuid, чтобы получить правильный, но кроме этого нет необходимости в быстром поиске. Эти UUID на самом деле предназначены только для извлечения связанных объектов данных Core. Данные обновляются не слишком часто.

SwiftUIEnthusiast 14.07.2024 00:37

Кроме того, мне нужно упорядочить эти структуры, но в основных данных это потребует переиндексации ненужных атрибутов индекса при обновлении или другого объекта, с которым нужно иметь связь, чтобы многие отношения были упорядочены, что означает, что о NSBatchInsertRequest не может быть и речи, который нельзя использовать. устанавливать отношения. Так что у меня нет другого выбора

SwiftUIEnthusiast 14.07.2024 00:46

Тогда один из способов, который вы можете использовать, — это просто записать каждый itemId в файл, названный по его id. При таком большом количестве файлов вам, вероятно, потребуется реализовать структуру каталогов. Вы можете взять первые четыре символа id и использовать их для ряда каталогов, так что в качестве идентификатора 1234-abc123-def789-0987 у вас будет путь к файлу 1/2/3/4/1234-abc123-def789-0987. Это даст вам около 150 файлов в конечном каталоге для 10 миллионов идентификаторов.

Paulw11 14.07.2024 00:48

Также важно понять, записаны ли эти 10 миллионов записей один раз или неоднократно. Одной 30-секундной настройки при первом запуске может быть достаточно, поскольку вы можете показать представление активности и объяснить, что происходит. Индивидуальное чтение и запись будут быстрыми.

Paulw11 14.07.2024 00:50

Вы также можете использовать SQLite напрямую, но я не думаю, что вы найдете способ создать 10 миллионов записей за нулевое время.

Paulw11 14.07.2024 00:52

Я понимаю. Мне было интересно, а как насчет PropertyListEncoders с двоичным выходным форматом, это быстрее и эффективнее, чем кодировщик json?

SwiftUIEnthusiast 14.07.2024 00:55

Просматривая некоторые другие ваши вопросы, я думаю, вам нужно переосмыслить свой фундаментальный подход. Действительно ли необходимо поддерживать строгий порядок в 10 миллионов (или даже 10 тысяч) элементов в плейлисте? Пользователь не может с этим справиться. Все, что им нужно видеть, это следующие 10 или 20 элементов и иметь возможность добавить новый элемент в начало очереди (или, возможно, в какой-то другой момент очереди). Вы даже можете получить больше предметов для игры, прокручивая список. Затем вы просто управляете элементами, которые будут воспроизводиться, и элементами, которые недавно воспроизводились.

Paulw11 14.07.2024 00:58

Давайте продолжим обсуждение в чате.

Paulw11 14.07.2024 00:59

Одна из главных причин заключается в том, что Apple Music и Spotify мгновенно загружают следующую очередь в правильном порядке плейлистов даже для плейлистов из 100 тысяч песен после тестирования, когда они загружаются для прослушивания в автономном режиме. Итак, я пытался реализовать подобное

SwiftUIEnthusiast 14.07.2024 01:01

Вы можете попробовать сохранить два UUID в файл или файл zip в виде строк, разделенных запятыми (или, что еще лучше, в его двоичном представлении). Например, id.uuidString, itemId.uuidString, затем при необходимости воссоздайте структуры/UUID из строк.

workingdog support Ukraine 14.07.2024 01:33

Как сохранить двоичное представление структуры?

SwiftUIEnthusiast 14.07.2024 01:49

Для uuid используйте: id.uuidString.data(using: .utf8) для всей структуры, которую вы можете использовать NSKeyedArchiver.archivedData...

workingdog support Ukraine 14.07.2024 01:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
13
103
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Чтобы преобразовать MetadataItem в двоичный код, а затем прочитать его обратно, вы можете попробовать этот подход, как показано в примере кода.

Обратите внимание: я не проверял это на эффективность.

struct MetadataItem: Identifiable, Codable {
    var id: UUID  // <--- here
    let itemId: UUID
}

struct ContentView: View {
    
    var body: some View {
        Text("testing")
            .onAppear {
                // test struct
                let metadataItem = MetadataItem(id: UUID(), itemId: UUID())
                print("---> metadataItem: \(metadataItem)\n")

                // convert to binary
                let binaryData = metadataItemToBinary(item: metadataItem)
                print("------> binaryData: \(binaryData)")
                
                // write to file
                
                // read from file
                
                // then recreate the struct
                if let restoredItem = binaryToMetadataItem(data: binaryData) {
                    print("\n---> restored item: \(restoredItem)")
                }
            }
    }
    
    func metadataItemToBinary(item: MetadataItem) -> Data {
        var data = Data()
        data.append(item.id.uuidString.data(using: .utf8)!)
        data.append(item.itemId.uuidString.data(using: .utf8)!)
        print("------> data: \(data)")
        return data
    }
    
    func binaryToMetadataItem(data: Data) -> MetadataItem? {
        guard data.count == 72 else {
            return nil
        }
        let idData = data.subdata(in: 0..<36)
        let itemIdData = data.subdata(in: 36..<72)
        
        if let id = UUID(uuidString: String(data: idData, encoding: .utf8)!),
           let itemId = UUID(uuidString: String(data: itemIdData, encoding: .utf8)!) {
            print("------> id: \(id)  itemId: \(itemId)")
            return MetadataItem(id: id, itemId: itemId)
        } else {
            // deal with errors
            return nil
        }
    }
}

РЕДАКТИРОВАТЬ-1:

для теста массива:

    .onAppear {
        // test array of MetadataItem
        let arr: [MetadataItem] = [MetadataItem(id: UUID(), itemId: UUID()), MetadataItem(id: UUID(), itemId: UUID())]
        print("---> arr: \(arr)\n")

        // convert to binary
        var binarr: [Data] = arr.map{metadataItemToBinary(item: $0)}

        // recover the structs
        print("---> restored")
        binarr.forEach{ data in
            if let restoredItem = binaryToMetadataItem(data: data) {
                print("---> item: \(restoredItem)")
            }
        }
    }

Как вместо этого добавить массив MetadataItem к экземпляру Data в вашей функции и также получить обратно массив MetadataItem? Могу ли я просто добавить прямо с помощью функции карты?

SwiftUIEnthusiast 14.07.2024 02:42

Здесь я описал инструменты, позволяющие преобразовать MetadataItem в двоичный формат, а затем преобразовать его обратно в структуру. Для такого массива просто добавьте в массив и сделайте то же самое.

workingdog support Ukraine 14.07.2024 02:46

вау, ваш метод был намного быстрее для миллиона элементов метаданных по сравнению с JSON Encoder или PropertyLisEncoder.. Интересно, будет ли двоичное представление намного быстрее. Действительно, это намного быстрее, чем любой из них.

SwiftUIEnthusiast 14.07.2024 02:53

обновил мой ответ с использованием массивов. Спасибо, рад, что это работает для вас.

workingdog support Ukraine 14.07.2024 03:01
Ответ принят как подходящий

Если кому-то интересно, после тестирования большего количества версий метода, предложенных «workdog», преобразование UUID в данные с использованием свойства .uuid и последующая запись на диск происходит намного быстрее, чем вместо использования строковой версии.

После тестирования вот что я получил за сохранение на диске 1 миллиона таких структур от самой медленной до самой быстрой...

PropertyListEncoder: 4,46 с.

JSONEncoder: 3,37 с.

Преобразование UUID в строку: 1,13 с.

Преобразование UUID в байты данных uuid: 345 мс.

func metadataItemsToBinary(items: [MetadataItem]) -> Data {
    var data = Data()
    for item in items {
        data.append(withUnsafeBytes(of: item.id.uuid) { Data($0) })
        data.append(withUnsafeBytes(of: item.itemId.uuid) { Data($0) })
    }

    return data
}

Другие вопросы по теме