Gestión de la memoria de edición de texto

Estoy construyendo una 'computadora portátil' de edición de texto basada en PIC. Tengo una tarjeta SD conectada al PIC y estoy usando un teclado y una pantalla LCD.

Mi problema es que quiero editar archivos realmente grandes como, por ejemplo, más de 300kB. Ahora tengo algunas opciones para el control de memoria:

  1. Almacene todo el archivo en la RAM externa. Insertar nuevos caracteres en el medio del archivo dará como resultado el reemplazo de cada byte a una dirección más alta. Desventaja: velocidad, ya que reemplazar 300.000 bytes llevará algún tiempo.
  2. Almacene bytes basados ​​en direcciones en la RAM externa. Separe la RAM en dos mitades: una contiene las direcciones de los bytes en la otra. Insertar caracteres significaría agregar bytes al final de la segunda mitad y agregar las direcciones al final del archivo a la primera mitad. Desventaja: el uso de RAM no es tan eficiente, al final tomaría mucho tiempo guardar el archivo ya que todas las direcciones se distribuyen a través de la RAM.
  3. Algo con un búfer de espacio en la RAM externa, lo que significaría dejar caracteres NUL en la tarjeta SD disponibles para futuras inserciones. Desventaja: el uso de la tarjeta SD no es tan eficiente, sin embargo, esto no es un problema. También: difícil de codificar (?).

Mi pregunta: estoy pensando en implementar el búfer de brecha, sin embargo, podría perderme algo. ¿Es un buffer de brecha la mejor manera de hacer esto?

Además aquí, la gente de StackOverflow podría encontrar interesante la pregunta.
Buen punto, lo copiaré allí también.
¿Por qué las opciones 1 y 2 asumen RAM externa, mientras que la opción 3 no? El uso de la técnica de búfer de brecha con la memoria RAM externa funcionaría bastante bien.
Ehm, edité mi publicación, quise decir un búfer de espacio en la RAM externa. Gracias por notarlo :-)
aunque @AnindoGhosh lo sugirió, los sitios consideran un abuso publicar la misma pregunta en varios lugares. debe adaptar una pregunta a la audiencia y no simplemente publicarla en todas partes a la vez.
también 4 respuestas sin una imagen bonita para decirme a quién votar.
No sabía eso, eliminé la Q de stackoverflow, de todos modos no obtuve ninguna respuesta allí.

Respuestas (5)

Mi primera reacción sería usar una RAM externa grande para mantener los datos que se están editando. Pero en lugar de hacer una copia de imagen directa, preasignaría bloques de RAM en una lista enlazada. Cada bloque tendría un puntero hacia adelante, un puntero hacia atrás, un tamaño usado y el búfer de datos de tamaño fijo.

Haga que cada bloque tenga una potencia de dos bytes de tamaño. Eso significa que se sabe que los pocos bits de dirección bajos para cada bloque son 0, por lo que no es necesario almacenarlos. Los punteros hacia adelante y hacia atrás podrían entonces ser de 16 bits y la longitud utilizada de 1 byte, para un total de 5 bytes de sobrecarga por bloque. Con bloques de 32 bytes, por ejemplo, esto representa una sobrecarga del 16 %. 1 Mbyte de RAM produciría 885 kBytes de almacenamiento de datos real, mucho más que los 300 kBytes que solicitó.

En efecto, este esquema crea una memoria lineal que puede tener datos insertados o eliminados en lugares arbitrarios con poca sobrecarga. Al agregar o eliminar, no tiene que mirar más allá de los bloques adyacentes en la cadena, por lo que se puede hacer fácilmente de forma instantánea en tiempo humano.

Mantienes dos cadenas, una para los bloques que contienen los datos y otra para los bloques no utilizados. El tipo correcto de edición en los lugares correctos podría causar una sobrecarga de fragmentación que haría que la memoria pareciera mucho más pequeña y, finalmente, incapaz de contener 300 kBytes. Sin embargo, es poco probable que ocurra una edición tan específica, y siempre puede hacer recombinaciones locales simples (si dos bloques adyacentes contienen menos datos que un bloque, luego fusione los dos y vuelva a colocar uno en la lista libre) que la mayoría de las veces mantendrá la fragmentación lo suficientemente bien. La fragmentación en realidad no importa hasta que necesite un bloque libre y no lo haya. Cuando eso sucede, realiza una desfragmentación y el usuario tiene que esperar unos segundos, pero esto será muy raro. Una buena estrategia sería realizar una desfragmentación automática en segundo plano durante los inevitables largos períodos de tiempo (desde el punto de vista del procesador) en los que el usuario no hace nada. Con esta estrategia, creo que es muy poco probable que te quedes sin bloques libres y tengas que desfragmentar mientras el usuario espera.

Este esquema es fácil de administrar incluso en un microcontrolador pequeño, la respuesta del usuario a las operaciones de edición es rápida y solo se accede a la tarjeta SD al principio y al final de las sesiones de edición.

Agregado:

Estaba considerando más la fragmentación después de escribir la publicación, y creo que se puede demostrar que siempre que realice una desfragmentación local simple en cualquier operación de inserción o eliminación, nunca perderá más de la mitad de la memoria disponible. Usando el ejemplo de memoria de 1 Mbyte con bloques de 32 bytes nuevamente, tiene garantizado al menos 442 kBytes de almacenamiento.

Después de una eliminación, fusiona el bloque del que se eliminó un byte con uno de sus vecinos si los dos juntos ahora contienen un bloque de datos o menos. En un byte agregado, transfiere datos del bloque actual a un vecino para hacer espacio en lugar de tomar un nuevo bloque a menos que ambos vecinos (y, por supuesto, el bloque actual) estén completamente llenos.

Todas estas operaciones nunca involucran más de 3 bloques, por lo que son instantáneas en tiempo humano. Si puede vivir con hasta la mitad de la memoria disponible sin usar, entonces no necesita hacer nada más. Sin embargo, hay poco daño en hacer una desfragmentación de fondo cuando no está pasando nada más.

¡También una solución interesante! Solo para asegurarme de que entiendo esto: ¿no es lo mismo que funciona el sistema de archivos FAT?
@Camil: No puedo decirlo porque nunca investigué FAT en ese detalle.
Bien, entonces lo asumiré, ya que he vuelto a leer tu publicación y la especificación. Es una gran idea y sin duda lo tomaré en cuenta!
@CamilStaps: el esquema de Olin no se parece en nada a FAT. Si bien FAT utiliza listas vinculadas para realizar un seguimiento de los grupos de datos, no permite que los bloques individuales (grupos) tengan tamaños variables. Si desea insertar o eliminar datos en medio de un archivo FAT, debe volver a escribir todos los datos que siguen a ese punto.
De acuerdo, debido al tamaño variable del grupo, es diferente, sí. Es mucho más flexible debido a eso. ¡Interesante, interesante! :-)
Este también es un buen enfoque, y también lo he pensado mucho (pero nunca implementé un editor que lo use). Entre otras cosas, hace que sea muy fácil implementar una pila de "deshacer" en su editor, al menos en la medida en que pueda evitar la desfragmentación, que destruirá gran parte de ese tipo de información. Mi editor de gap-buffer tenía una función de deshacer muy limitada, que solo funcionaba siempre que no hubieras movido el cursor desde la última inserción/eliminación.

Este es un problema básico de programación que puedes resolver más cómodamente en una PC sin preocuparte inmediatamente por el PIC, ¡aunque obviamente estarás buscando una pequeña solución eficiente!

Otro enfoque, viable si puede especificar un carácter o secuencia que no puede aparecer en el texto, es reemplazar algunos bytes en su búfer de texto (que podría ser una memoria RAM externa) con esa secuencia seguida de un número. Esto actúa como un punto de interrupción de software en un depurador: cuando File/Save vea ese punto de interrupción, busque el número en una lista de cambios. Contendrá los caracteres que eliminó más cualquier modificación.

Si la lista de cambios se llena, debe volver a sincronizar el documento para vaciar la lista; esto también podría ser una operación de guardado automático. (¡Hasta ese punto, también tiene una función de deshacer!)

Desea minimizar la cantidad de escrituras en la tarjeta SD, por lo que solo guarda automáticamente y las operaciones de archivo/guardado del usuario en lugar de escribir cada cambio en SD.

¡Ese es un enfoque muy agradable y ciertamente lo pensaré! Podría aceptar su respuesta, pero esperaré a que otros vengan con otras respuestas también.
Deje de aceptarlo durante unos días, puede haber otras buenas respuestas.

Para un editor de texto simple, el método de búfer de espacios es muy efectivo. Lo usé cuando escribí un editor para mi computadora Ferguson Big Board (CP/M basada en Z80) a principios de la década de 1980, y me resultó muy fácil trabajar con él. Por supuesto, pude usar algunas instrucciones Z80 de bajo nivel que lo hicieron muy eficiente para mover el texto a través del espacio.

Este método también se expande muy bien para editar varios archivos simultáneamente, suponiendo que todos encajen en la memoria RAM disponible. Simplemente los concatena, y la brecha existe solo en el archivo que actualmente tiene el "foco" de edición. Necesita mantener algunos punteros (o marcadores) para los límites del archivo y manejarlos según sea necesario al mover el espacio.

¡+1 por la idea de editar varios archivos simultáneamente! Hay suficientes caracteres ASCII que se pueden usar como marcadores para los límites del archivo. ¡Gracias por el consejo!
@CamilStaps: En realidad, advertiría contra la creación de un editor que no sea "limpio de 8 bits" (es decir, que pueda manejar datos binarios arbitrarios). Tal vez su aplicación sea más restringida, pero mi editor era para el desarrollo de software general, y con frecuencia es útil poder abrir un archivo arbitrario (incluso un archivo binario) para echar un vistazo rápido al interior. En otras palabras, recomendaría usar punteros en lugar de marcadores (y no solo para los límites de los archivos).
Sí, ese es un punto. Para la primera versión, esto será solo texto sin formato, por lo que STX y ETX o caracteres especiales como 176 y superiores estarían bien, pero para compatibilidad futura, ¡es una buena idea usar punteros! :-)

El uso de RAM externa es sin duda el mejor esquema. Un búfer de brecha puede dividir el flujo de prueba en la mitad inferior en un extremo de su grupo de memoria y la mitad superior en el otro lado del grupo de memoria. Las inserciones y eliminaciones locales se pueden realizar a lo largo de la brecha simplemente moviendo pequeñas cantidades de datos a través del margen de la brecha de una forma u otra.

El uso de un gap buffer puede ser una buena estrategia para usar de forma dinámica. Puede enfocarse en abrir un agujero en la ubicación de enfoque de edición actual para que los nuevos datos tengan un lugar para ir. Inicialmente, puede colocar una etiqueta de redirección en la ubicación de enfoque eliminando solo el texto existente suficiente para dejar espacio para la inserción de la etiqueta. Luego, en un búfer de trabajo separado señalado por la etiqueta, puede capturar el texto eliminado inicialmente y luego agregar cualquier texto nuevo que se esté ingresando. Con una programación inteligente, puede ejecutar un proceso paralelo que divide la imagen en memoria para actualizar el espacio y luego elimina la etiqueta de redirección y el búfer de edición temporal asociado con ella.

Este esquema permite reclamar dinámicamente una lista de etiquetas de redirección (y sus búferes de trabajo) para que rara vez obtenga una condición de "lista llena". La premisa es, por supuesto, que durante una sesión de edición, la tasa promedio de llegada de nuevos datos es más lenta de lo que el programa puede mover la región de brecha y recuperar las etiquetas de redirección.

Otra nota sobre la idea de insertar etiquetas en el flujo de texto. Las etiquetas también se pueden usar para otras cosas además de marcar dónde ha colocado una etiqueta de redirección. Puede ser una buena idea marcar las etiquetas de tal manera que pueda encontrarlas en el flujo de texto, ya sea escaneando hacia adelante o hacia atrás a través del texto. Por supuesto, lo más simple es incrustar la etiqueta con el mismo indicador de etiqueta en cada extremo.

Mucho dependerá de los tipos de operaciones que necesite realizar. Si puede almacenar datos temporalmente como una lista de líneas de longitud fija, puede usar un asignador de fragmentos de tamaño fijo para almacenar las líneas individuales en una secuencia arbitraria y luego usar una matriz de RAM o un búfer de espacio (posiblemente en un dispositivo de RAM externo ) para convertir números de línea lineales en ubicaciones de fragmentos. También se podrían usar líneas de longitud variable con un asignador de fragmentos de longitud variable, o usar fragmentos de tamaño fijo pero hacer que cada fragmento contenga un número que especifique el número de bytes utilizados o la longitud del siguiente fragmento. Use un búfer de RAM para contener la línea actual, de modo que solo se necesite acceder a la matriz de fragmentos cuando el cursor se mueva fuera de ella. Las tarjetas SD son lo suficientemente grandes como para que incluso si cada línea se rellena a 256 bytes en un archivo temporal, eso probablemente no debería No plantea demasiada dificultad. Cada 256 bytes contendría un byte FF y una longitud, o un número de bloque de dos bytes; eso permitiría archivos de hasta unos 8 megabytes y 32767 líneas.