Программный комплекс по биоинформатике. Сохранение прогресса алгоритмов

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

  • Алгоритмы распознавания и некоторые вспомогательные алгоритмы (например, алгоритмы отбора предикатов для иерархических композиций) работают достаточно долго (от 10 минут до нескольких часов). Хотелось бы иметь возможность в любой момент прервать работу алгоритма и вернуться к нему позже. (При прерывании выполнения программы пользователем сохранение состояния алгоритма возможно за счет использования Runtime.addShutdownHook.)
  • Во многих случаях имеет ценность информация, связанная с алгоритмом (например, порядок используемой модели для распознавания). Желательно, чтобы эту информацию не требовалось хранить вручную.

Наиболее очевидный способ хранения структурированных данных в Java, который и был использован — сериализация средствами интерфейса java.io.Serializable. У этого метода есть недостатки (например, при изменении структуры наследования класса или его переименовании десериализация перестает работать), но при разумном процессе разработки количество встреч с ними минимально. Более того, разработка с оглядкой на сериализацию побуждает с самого начала создавать правильную иерархию классов и подбирать для классов подходящие поля.

Понятие алгоритма сосредоточено в интерфейсе Launchable:

public interface Launchable extends Serializable {
	
	/**
	 * Выполняет алгоритм или задание.
	 * 
	 * @param env
	 *    окружение, в котором выполняется алгоритм или задание
	 */
	public void run(Env env);
	
	/**
	 * Возвращает окружение, в котором выполняется алгоритм или задание.
	 * 
	 * @return
	 *    окружение
	 */
	public Env getEnv();
}

Env — среда, в которой выполняется алгоритм; она, в частности, предоставляет доступ к параллельному выполнению кода (за счет ExecutorService) и выводу информации для пользователя.

Пример

Сохраняемый алгоритм, который производит некоторую операцию над целыми числами от 0 до max - 1, может выглядеть так:

import java.io.File;
import java.io.IOException;

import ua.kiev.icyb.bio.Env;
import ua.kiev.icyb.bio.Launchable;


public class Enumerator implements Launchable {

	/** 
	 * Версия для сериализации (требуется согласно 
	 * спецификации интерфейса Serializable). 
	 */
	private static final long serialVersionUID = 1L;
	
	/** Среда, в которой выполняется задание. */
	private transient Env env;
	
	/** Верхняя граница перечисления. */
	private final int max;
	
	/** Текущее обрабатываемое число. */
	private int last;
	
	public Enumerator(int max) {
		this.max = max;
		this.last = 0;
	}
	
	private void eval(int number) {
		getEnv().debug(1, "eval(" + number + ")");
		// Эмуляция трудоемких вычислений
		try { Thread.sleep(1000); } catch (InterruptedException e) { }
		getEnv().debug(1, "Finished eval(" + number + ")");
	}
	
	@Override
	public void run(Env env) {
		this.env = env;
		for (int i = this.last; i < this.max; ) {
			eval(i);
			this.last = ++i;
			// Сохранить прогресс задания
			getEnv().saveProgress();
		}
	}

	@Override
	public Env getEnv() {
		return this.env;
	}
}

У алгоритма есть два параметра, которые надо сохранять — max и последнее обработанное число last. Если прервать работу алгоритма и затем загрузить его заново, вычисления начнутся с того же числа, на котором они закончились при прерывании.

public class Enumerator implements Launchable {

	// код пропущен

	/**
	 * Создает задание по перечислению чисел от 0 до 9.
	 * Прогресс задания сохраняется в файл.
	 */
	public static void main(String[] args) throws IOException {
		// Имя файла, в который сохраняется задание
		final String filename = "enumerator.run";
		
		Env env = new Env();
		env.setDebugLevel(2);
		
		Enumerator enumerator = new Enumerator(10);
		if (new File(filename).isFile()) {
			enumerator = env.load(filename);
		}
		env.run(enumerator, filename);
	}
}

Приблизительный результат выполнения:

% java Enumerator
Уровень отладки: 2
eval(0)
Finished eval(0)
eval(1)
Finished eval(1)
eval(2)
^C
% java Enumerator
Уровень отладки: 2
eval(2)
Finished eval(2)
eval(3)
Finished eval(3)
...

Видно, что функция eval(2) выполняется дважды. В первый раз во время ее выполнения происходит прерывание, так что при запуске алгоритма заново вычисления надо проводить заново.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *