Введение
Многие сталкивались с проблемой того, что нужно быстро записать данные на flash-ку, но
Задумавшись об этом, я начал размышлять: каким же образом можно ускорить запись, не покупая новый девайс. О том что я придумал, и почему у меня ничего не получилось и будет этот пост.
Общее описание идеи
Для начала стоит задуматься: что же такое файл? Ход моих мыслей был прост: файл — последовательность байт. Байт — последовательность бит.
Далее стоит задуматься каким образом устроена flash-ка. Взгляд дилетанта таков: имеется матрица из ячеек (на сколько я знаю: 1 ячейка — 1 полевой транзистор). В одну ячейку можно записать 1 бит информации. Скорость записи информации зависит от скорости, с которой контроллер сможет установить ячейку в нужное состояние.
Идем далее и понимаем, что увеличить скорость удастся только в том случае, если не писать часть информации в ячейку. Последнее утверждение, на первый взгляд, кажется абсурдным, но давайте вспомним о структуре файла. По сути информация — случайный набор каких-то цифр. А где есть случайность, там есть и вероятность. Тут во мне снова проснулся дилетант, и я предположил что число 1 и 0 в файле примерно одинаково, и вероятность их появления = 0,5.
А что, если при записи мы будем пропускать те ячейки, которые установлены в то состояние, которое нам надо? Значит скорость записи должна немного увеличиться. Для этого нам нужно установить ячейки в то состояние, которое было бы нам известно. Я пробовал чередовать 1 и 0.
Реализация
Для реализации нам необходимо знать несколько вещей:
- Скорость записи на флэшку
- Скорость чтения с флэшки (не очень нужна, но для получения полной картины узнать можно)
- Скорость сравнения битов
Идем дальше.
Генерация случайных файлов
Напишем небольшую программу, которая будет генерировать случайный файл размером 1Мб. Тут и далее будем использовать коктейль C и C++ (ну так получилось).
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <time.h>
#define fsize 1048576
using namespace std;
int main(){
fstream f;
f.open("rand_file", ios::out);
srand(time(0));
for(int i=0; i<fsize; ++i){
f<<(char)(rand()%255);
}
f.close();
return 0;
}
Думаю тут все понятно. Генерируем случайные байты и пишем их в файл.
Измерение скоростей
Снова начинаем разогревать компилятор, и, скармливаем ему следующий код:
#include <iostream>
#include <fstream>
#include <sys/time.h>
#include <fcntl.h>
using namespace std;
inline long long gettimeus()
{
struct timeval tv;
gettimeofday( &tv, NULL );
return (long long) tv.tv_sec * 1000000LL + (long long) tv.tv_usec;
}
int main(int argc, char **argv)
{
int in = open(argv[1], O_RDONLY);
int out = open(argv[2], O_WRONLY | O_CREAT);
int size = lseek(in, 0, SEEK_END);
lseek(in, 0, SEEK_SET);
if (size > 0){
char *fin = new char[size];
long long tmIn = gettimeus();
for(int i=0; i<size; ++i)
read (in, &(fin[i]), 1);
tmIn = gettimeus() - tmIn;
cout<<"Time read: "<<tmIn<<endl;
long long tmOut = gettimeus();
for(int i=0; i<size; ++i)
write(out, &(fin[i]), 1);
tmOut = gettimeus() - tmOut;
cout<<"Time write: "<<tmOut<<endl;
long long tmIF = gettimeus();
int s = size*8;
for(int i=0; i<s; ++i)
if (true == false);
tmIF = gettimeus() - tmIF;
cout<<"Time if: "<<tmIF<<endl;
delete [] fin;
}
close(in);
close(out);
return 0;
}
Тут стоит уточнить некоторые моменты:
Функция gettimeus() необходима для подсчета времени, но у пользователей ОС Windows скорее всего работать не будет, ибо у них нету sys/time.h. Счастливые обладатели linux дистрибутивов никаких затруднений при компиляции почувствовать не должны. Далее немного по коду. Программа принимает два аргумента:
1. Файл для чтения.
2. Файл для записи.
И поочередно пытается прочитать один файл, и записать в другой файл, попутно измеряя время, потраченное на эти действия. Для этих целей используются системные вызовы read() и write(). Ну и в качестве бонуса идет замер времени сравнений.
Запустим программу 10 раз, а полученные значения усредним:
Read | Write | If |
---|---|---|
296127 | 1082888,7 | 24024,9 |
Как и следовало ожидать самая быстрая операция — сравнение, а самая медленная — запись. Для дальнейших расчетов перейдем к другим единицам измерения времени. Для этого поделим все времена на время сравнения. В результате получим:
Сравнение — 1.
Запись — 45,07.
Чтение — 12,33.
Все. Теперь нам ничего не мешает начать моделирование.
Оценка производительности
Идея оценки проста: перегоняем файл в память, перегоняем каждый байт в битовое представление. И начинаем подсчет времени перезаписи.
#include <iostream>
#include <fstream>
#include <sys/time.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
using namespace std;
const double WRITE = 45.07;
const double READ = 12.33;
const double IF = 1.0;
bool *translateToBitArray(const char *array, int size);
int main(int argc, char **argv)
{
if (argc > 1){
int in = open(argv[1], O_RDONLY);
int size = lseek(in, 0, SEEK_END);
lseek(in, 0, SEEK_SET);
if (size > 0){
char *fin = new char[size];
bool *bFOut = new bool[size*8]; //Размер файла в битах
for(int i=0; i<size; ++i)
read (in, &(fin[i]), 1);
bFOut[0] = true;
for(int i=1; i<size*8; ++i) //Заполняем наш файл чередующимися битами
bFOut[i] = !bFOut[i-1];
bool *bArr = translateToBitArray(fin, size);//Транслируем данные в битовый массив
double timeLinear = 0;
double timeOpt = 0;
timeLinear = WRITE*size*8; //Если писать все биты
bool flag = bFOut[0]; //Начальное значение
for(int i=0; i<size*8; ++i){ //Оптимальная запись
timeOpt += IF;
if (bArr[i] != flag){
bFOut[i] = bArr[i];
timeOpt += WRITE;
}
flag = !flag;
}
//Проверка
flag = false;
for(int i=0; i<size*8; ++i)
if (bArr[i] != bFOut[i]){
flag = true;
break;
}
if (flag)
printf("ERROR\n");
printf("Time linear = %.6lf\nTime opt = %.6lf\n",timeLinear, timeOpt);
delete [] fin;
delete [] bFOut;
}
close(in);
}
return 0;
}
bool *translateToBitArray(const char *array, int size)
{
bool *bArr = new bool[size*8];
bool *tmp = new bool[8];
int pos = 0;
for(int i=0; i<size; ++i){
unsigned char c = array[i];
int ppp = 8;
fill_n(tmp, 8, 0);
while (c != 0){
tmp[ppp--] = (c & 1);
c = c >> 1;
}
memcpy(&(bArr[pos]), tmp, 8);
pos += 8;
}
delete [] tmp;
return bArr;
}
Никаких проверок, никаких подсказок. Если работает — хорошо, если нет — вылетаем и все. Запускаем программу 10 раз на различных файлах (сперва случайных, а потом и настоящих).
Итоги моделирования
В итоге получаем ускорение по записи ~1.71 раза по отношению к обычной записи.
Ложка дегтя
К сожалению ничто (кроме наследства) в мире не бывает бесплатным, и, за ускорение записи придется платить замедлением удаления данных, так как необходимо поддерживать чередование битов. Плюс усложнение процедур восстановления после сбоев и т.д.
Выводы
Выводы неутешительные.
Я не нашел ни одного способа записать на флэшку отдельные биты. Удивляться тут конечно нечему (зачем же тогда нужен контроллер, если бы ОС приходилось выполнять его функции). Усложнение функций контроллера тоже не предвидится, так как случайные сбои (читай: «Вася выдернул флэшку до того, как контроллер успел восстановить структуру») могут привести к ненадежность записи и недоверию к производителю.
Конечно же плюсы тоже есть:
- Увеличение записи почти в 2 раза.
- Производителю не нужно разрабатывать «мега новые» технологии сверхбыстрых ячеек, а всего лишь усложнить процедуру записи (ну и, естественно, продавать накопители на порядок дороже).
- Возможно (но не факт) увеличится срок службы ячеек.
Вот как-то так. Жду Ваших комментариев.