IO файла IO замедляется с большими последовательными записями
Я написал программу очистки дисков, предназначенную для безопасного перезаписи свободного места на дисках. Сначала все отлично работает, потом со временем скорость резко снижается. У меня есть один диск емкостью 1 ТБ, который начинается со скорости около 120 МБ / с, а затем медленно падает до 70. Сначала я подумал, что это диск, поэтому я протестировал его на своих дисках RAID0 с велоцираптором, которые набрали 160 МБ / с в течение почти полминуты, прежде чем медленно падение примерно до 110. Это не похоже на заполнение кэша, потому что требуется около минуты, чтобы полностью замедлиться.
Во-первых, проблема заключается в том, что я потенциально записываю данные на диск, или это действительно нормальная функция для жестких дисков на других языках?
Во-вторых, могу ли я увидеть какую-либо потенциальную выгоду от перехода на NIO, связанную со скоростью? Использование ISAAC wipe является многопоточным, поэтому узким местом является, по-видимому, просто скорость записи.
Наконец, возможно, это может быть в моих расчетах скорости. Но я оставил это очень просто, поэтому я не понимаю, как это могло быть.
РЕДАКТИРОВАТЬ: (немного информации)
Оба являются обычными магнитными приводами. Накопитель на 1 ТБ - 7200 об / мин. Установка raid0 составляет два WD 10000 об / мин. Запуск Windows 7 Ultimate.
Java-версия "1.8.0_45". Java(TM) SE Runtime Environment (сборка 1.8.0_45-b15). Java HotSpot(TM) 64-разрядная серверная виртуальная машина (сборка 25.45-b02, смешанный режим).
Вы можете проверить этот работающий пример: (cleaner.DriveCleaner)
package cleaner;
import java.awt.EventQueue;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.SecureRandom;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JProgressBar;
import javax.swing.text.NumberFormatter;
/**
* @author Colby
*/
public class DriveCleaner extends javax.swing.JFrame {
public DriveCleaner() {
initComponents();
refreshDrives();
}
protected static boolean running = false;
private Thread worker;
private void refreshDrives() {
File[] roots = File.listRoots();
drives.setModel(new DefaultComboBoxModel(roots));
}
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
buttonGroup1 = new javax.swing.ButtonGroup();
jLabel1 = new javax.swing.JLabel();
jSeparator1 = new javax.swing.JSeparator();
drives = new javax.swing.JComboBox();
normSelect = new javax.swing.JRadioButton();
randSelect = new javax.swing.JRadioButton();
jLabel2 = new javax.swing.JLabel();
jLabel3 = new javax.swing.JLabel();
passes = new javax.swing.JComboBox();
jLabel4 = new javax.swing.JLabel();
runButton = new javax.swing.JButton();
jSeparator2 = new javax.swing.JSeparator();
jSeparator3 = new javax.swing.JSeparator();
progress = new javax.swing.JProgressBar();
jMenuBar1 = new javax.swing.JMenuBar();
jMenu1 = new javax.swing.JMenu();
jMenuItem1 = new javax.swing.JMenuItem();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("DriveCleaner V1.0");
jLabel1.setFont(new java.awt.Font("Consolas", 2, 17)); // NOI18N
jLabel1.setText("DriveCleaner");
buttonGroup1.add(normSelect);
normSelect.setText("Simple Clean");
normSelect.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
normSelectActionPerformed(evt);
}
});
buttonGroup1.add(randSelect);
randSelect.setSelected(true);
randSelect.setText("ISAAC 256 Clean");
randSelect.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
randSelectActionPerformed(evt);
}
});
jLabel2.setText("Drive:");
jLabel3.setText("Passes:");
passes.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "1", "2", "4", "8", "16", "32", "64", "128" }));
passes.setSelectedIndex(2);
jLabel4.setText("Method:");
runButton.setText("Clean");
runButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
runButtonActionPerformed(evt);
}
});
jSeparator2.setOrientation(javax.swing.SwingConstants.VERTICAL);
progress.setString("");
progress.setStringPainted(true);
jMenu1.setText("File");
jMenuItem1.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F5, 0));
jMenuItem1.setText("Refresh Drives");
jMenuItem1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jMenuItem1ActionPerformed(evt);
}
});
jMenu1.add(jMenuItem1);
jMenuBar1.add(jMenu1);
setJMenuBar(jMenuBar1);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jSeparator3)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jLabel1)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(drives, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel2))
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(passes, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel3))
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(randSelect)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(normSelect))
.addComponent(jLabel4))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 31, Short.MAX_VALUE)
.addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, 12, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(runButton, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(progress, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel2)
.addComponent(jLabel3)
.addComponent(jLabel4))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(drives, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(passes, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(randSelect)
.addComponent(normSelect)))
.addComponent(jSeparator2)
.addComponent(runButton, javax.swing.GroupLayout.DEFAULT_SIZE, 43, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jSeparator3, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progress, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
pack();
}// </editor-fold>
private void runButtonActionPerformed(java.awt.event.ActionEvent evt) {
if (running) {
runButton.setText("Halting");
runButton.setEnabled(false);
new Thread() {
@Override
public void run() {
try {
running = false;
worker.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
progress.setString("");
progress.setValue(0);
runButton.setEnabled(true);
runButton.setText("Clean");
}
});
}
}.start();
} else {
running = true;
runButton.setText("Stop");
worker = new Thread(new Runnable() {
@Override
public void run() {
try {
Wipe.wipe(progress, (File) drives.getSelectedItem(), Integer.parseInt((String) passes.getSelectedItem()), useRandomData);
} catch (Exception e) {
e.printStackTrace();
}
}
});
worker.start();
}
}
private void jMenuItem1ActionPerformed(java.awt.event.ActionEvent evt) {
refreshDrives();
}
private void randSelectActionPerformed(java.awt.event.ActionEvent evt) {
useRandomData = true;
}
private void normSelectActionPerformed(java.awt.event.ActionEvent evt) {
useRandomData = false;
}
protected static boolean useRandomData = true;
public static void main(String args[]) {
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(DriveCleaner.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(DriveCleaner.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(DriveCleaner.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(DriveCleaner.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new DriveCleaner().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.ButtonGroup buttonGroup1;
private javax.swing.JComboBox drives;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel jLabel4;
private javax.swing.JMenu jMenu1;
private javax.swing.JMenuBar jMenuBar1;
private javax.swing.JMenuItem jMenuItem1;
private javax.swing.JSeparator jSeparator1;
private javax.swing.JSeparator jSeparator2;
private javax.swing.JSeparator jSeparator3;
private javax.swing.JRadioButton normSelect;
private javax.swing.JComboBox passes;
private javax.swing.JProgressBar progress;
private javax.swing.JRadioButton randSelect;
private javax.swing.JButton runButton;
// End of variables declaration
}
class ISAAC {
public ISAAC(int ai[]) {
cryptArray = new int[256];
keySetArray = new int[256];
System.arraycopy(ai, 0, keySetArray, 0, ai.length);
initializeKeySet();
}
public int getNextKey() {
if (keyArrayIdx-- == 0) {
generateNextKeySet();
keyArrayIdx = 255;
}
return keySetArray[keyArrayIdx];
}
public void generateNextKeySet() {
cryptVar2 += ++cryptVar3;
for (int i = 0; i < 256; i++) {
int j = cryptArray[i];
if ((i & 3) == 0) {
cryptVar1 ^= cryptVar1 << 13;
} else if ((i & 3) == 1) {
cryptVar1 ^= cryptVar1 >>> 6;
} else if ((i & 3) == 2) {
cryptVar1 ^= cryptVar1 << 2;
} else if ((i & 3) == 3) {
cryptVar1 ^= cryptVar1 >>> 16;
}
cryptVar1 += cryptArray[i + 128 & 0xff];
int k;
cryptArray[i] = k = cryptArray[(j & 0x3fc) >> 2] + cryptVar1 + cryptVar2;
keySetArray[i] = cryptVar2 = cryptArray[(k >> 8 & 0x3fc) >> 2] + j;
}
}
public void initializeKeySet() {
int i1;
int j1;
int k1;
int l1;
int i2;
int j2;
int k2;
int l = i1 = j1 = k1 = l1 = i2 = j2 = k2 = 0x9e3779b9;
for (int i = 0; i < 4; i++) {
l ^= i1 << 11;
k1 += l;
i1 += j1;
i1 ^= j1 >>> 2;
l1 += i1;
j1 += k1;
j1 ^= k1 << 8;
i2 += j1;
k1 += l1;
k1 ^= l1 >>> 16;
j2 += k1;
l1 += i2;
l1 ^= i2 << 10;
k2 += l1;
i2 += j2;
i2 ^= j2 >>> 4;
l += i2;
j2 += k2;
j2 ^= k2 << 8;
i1 += j2;
k2 += l;
k2 ^= l >>> 9;
j1 += k2;
l += i1;
}
for (int j = 0; j < 256; j += 8) {
l += keySetArray[j];
i1 += keySetArray[j + 1];
j1 += keySetArray[j + 2];
k1 += keySetArray[j + 3];
l1 += keySetArray[j + 4];
i2 += keySetArray[j + 5];
j2 += keySetArray[j + 6];
k2 += keySetArray[j + 7];
l ^= i1 << 11;
k1 += l;
i1 += j1;
i1 ^= j1 >>> 2;
l1 += i1;
j1 += k1;
j1 ^= k1 << 8;
i2 += j1;
k1 += l1;
k1 ^= l1 >>> 16;
j2 += k1;
l1 += i2;
l1 ^= i2 << 10;
k2 += l1;
i2 += j2;
i2 ^= j2 >>> 4;
l += i2;
j2 += k2;
j2 ^= k2 << 8;
i1 += j2;
k2 += l;
k2 ^= l >>> 9;
j1 += k2;
l += i1;
cryptArray[j] = l;
cryptArray[j + 1] = i1;
cryptArray[j + 2] = j1;
cryptArray[j + 3] = k1;
cryptArray[j + 4] = l1;
cryptArray[j + 5] = i2;
cryptArray[j + 6] = j2;
cryptArray[j + 7] = k2;
}
for (int k = 0; k < 256; k += 8) {
l += cryptArray[k];
i1 += cryptArray[k + 1];
j1 += cryptArray[k + 2];
k1 += cryptArray[k + 3];
l1 += cryptArray[k + 4];
i2 += cryptArray[k + 5];
j2 += cryptArray[k + 6];
k2 += cryptArray[k + 7];
l ^= i1 << 11;
k1 += l;
i1 += j1;
i1 ^= j1 >>> 2;
l1 += i1;
j1 += k1;
j1 ^= k1 << 8;
i2 += j1;
k1 += l1;
k1 ^= l1 >>> 16;
j2 += k1;
l1 += i2;
l1 ^= i2 << 10;
k2 += l1;
i2 += j2;
i2 ^= j2 >>> 4;
l += i2;
j2 += k2;
j2 ^= k2 << 8;
i1 += j2;
k2 += l;
k2 ^= l >>> 9;
j1 += k2;
l += i1;
cryptArray[k] = l;
cryptArray[k + 1] = i1;
cryptArray[k + 2] = j1;
cryptArray[k + 3] = k1;
cryptArray[k + 4] = l1;
cryptArray[k + 5] = i2;
cryptArray[k + 6] = j2;
cryptArray[k + 7] = k2;
}
generateNextKeySet();
keyArrayIdx = 256;
}
public int keyArrayIdx;
public int keySetArray[];
public int cryptArray[];
public int cryptVar1;
public int cryptVar2;
public int cryptVar3;
}
class Wipe {
private static BlockingQueue<byte[]> buffers, randata;
private static class SecureDataCreator implements Runnable {
@Override
public void run() {
try {
SecureRandom seeder = new SecureRandom();
ISAAC rand = new ISAAC(new int[]{seeder.nextInt(), seeder.nextInt(), seeder.nextInt(), seeder.nextInt()});
do {
byte[] next = buffers.take();
for (int i = 0; i < next.length; i++) {
next[i] = (byte) rand.getNextKey();
}
randata.add(next);
} while (true);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void wipe(JProgressBar prog, File drive, int numPasses, boolean random) throws IOException, InterruptedException, ParseException {
NumberFormat format = NumberFormat.getPercentInstance();
format.setMinimumFractionDigits(2);
NumberFormatter formatter = new NumberFormatter(format);
prog.setValue(0);
prog.setString("Opening file handle");
File wipeFile = new File(drive, "wipefile.dat");
wipeFile.deleteOnExit();
try (RandomAccessFile raf = new RandomAccessFile(wipeFile, "rw")) {
try {
while (wipeFile.getFreeSpace() > raf.length()) {
try {
raf.setLength(drive.getFreeSpace());
} catch (IOException e) {
raf.setLength(0);
}
}
int dataSize = 1024 * 1024 * 32;
int numCores = Runtime.getRuntime().availableProcessors();
boolean needWorkers = buffers == null && random;
if (needWorkers) {
for (int i = 0; i < numCores; i++) {
Thread worker = new Thread(new SecureDataCreator());
worker.setPriority(Thread.MIN_PRIORITY);
worker.start();
}
buffers = new ArrayBlockingQueue<>(numCores + 1);
randata = new ArrayBlockingQueue<>(numCores + 1);
for (int i = 0; i < numCores + 1; i++) {
buffers.add(new byte[dataSize]);
}
}
long startTime = System.nanoTime();
byte[] data = random ? null : new byte[dataSize];
for (int pass = 0; DriveCleaner.running && (pass < numPasses); pass++) {
raf.seek(0);
do {
long writeLen = dataSize;
if (raf.getFilePointer() + writeLen > raf.length()) {
writeLen = raf.length() - raf.getFilePointer();
}
if (random) {
data = randata.take();
}
raf.write(data, 0, (int) writeLen);
if (random) {
buffers.add(data);
}
double total = numPasses * raf.length();
double done = (pass * (raf.length() - 1)) + raf.getFilePointer();
float percent = (float) (done / total);
double elapsed = (System.nanoTime() - startTime) / (1000000D * 1000D);
float bytesPerSec = (float) (done / elapsed) / (1024F * 1024F);
prog.setValue((int) percent);
prog.setString("Cleaning " + drive + ". Pass #" + pass + "/" + numPasses + ". "
+ formatter.valueToString(new Float(percent))
+ " @" + (int) bytesPerSec + "mBps");
} while (raf.getFilePointer() < raf.length() && DriveCleaner.running);
prog.setString("Complete.");
}
System.out.println("done");
} finally {
raf.setLength(0);
}
} finally {
wipeFile.delete();
}
}
}
1 ответ
У меня есть несколько предложений, чтобы изолировать источник проблемы, так как пока нет никаких признаков того, происходит ли это от JVM, ОС или оборудования:
- Ваш генератор случайных чисел может исчерпать энтропию. В качестве теста используйте нули вместо случайного вызова.
- Измерьте время сборки мусора JVM. Возможно, что создание многих временных объектов массива вызывает GC-паузы.
- Попробуйте запустить свою программу Java на Linux (например, с загрузочного USB или CD), чтобы увидеть, возникает ли та же проблема.
- Попробуйте другую реализацию JVM (например, OpenJDK и Oracle JDK), чтобы увидеть, возникает ли та же проблема.