SciChart Android 实时绘图:如何最大限度地提高绘图速度?
SciChart Android Real Time Graphing: How to maximize graphing speed?
我正在使用 Scichart 为 android 编写一个实时绘图应用程序。我一直在使用
FastLineRenderableSeries 作为我的数据系列的包装器
但我想知道 还有哪些其他技术可以使用 Android SciChart 来最大化绘图速度?
特别是当我使用 IXyDataSeries 并将 x 轴大小从 10,000 点增加到 100,000 点时,我注意到性能下降。在我向我的 IXyDataSeries.
添加大约 90,000 个点之前,绘图的速度一直保持很快
谢谢大家。我是 Whosebug 的新手……与其说是 CS 人员,不如说是 mechE。
这是我的 graphFragment class,它以字符串形式接收 UDP 传感器数据,将其拼接并添加到 IXyDataSeries。
public class GraphFragment extends Fragment {
//Various fields...
//UDP Settings
private UdpClient client;
private String hostname;
private int remotePort;
private int localPort;
//Use to communicate with UDPDataClass
private Handler handler;
private boolean listenerExists = false;
private int xBound = 100000; //**Graphing Slows if xBound is TOO large**
private int yBound = 5000;
private boolean applyBeenPressed = false;
private GraphDataSource dataSource; //Gets data from UDPDataClass
private SciChartSurface plotSurface; //Graphing Surface
protected final SciChartBuilder sciChartBuilder = SciChartBuilder.instance();
//Data Series containers
//Perhaps it would be better to use XyySeries here?
private final IXyDataSeries<Double, Double> dataSeriesSensor1 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor2 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor3 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor4 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor5 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor6 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private ArrayList<IXyDataSeries<Double,Double>> dataSeriesList = new ArrayList<>(Arrays.asList(dataSeriesSensor1,dataSeriesSensor2,dataSeriesSensor3,dataSeriesSensor4, dataSeriesSensor5, dataSeriesSensor6));
private ArrayList<Double> xCounters = new ArrayList<>(Arrays.asList(0.0,0.0,0.0,0.0,0.0,0.0));
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View frag = inflater.inflate(R.layout.graph_fragment, container, false);
plotSurface = (SciChartSurface) frag.findViewById(R.id.dynamic_plot);
dataSource = new GraphDataSource(); //Run the data handling on a separate thread
dataSource.start();
UpdateSuspender.using(plotSurface, new Runnable() {
@Override
public void run() {
final NumericAxis xAxis = sciChartBuilder.newNumericAxis().withVisibleRange(0,xBound).build();
final NumericAxis yAxis = sciChartBuilder.newNumericAxis().withVisibleRange(0,yBound).build();
//These are wrappers for the series we will add the data to...It contains the formatting
final FastLineRenderableSeries rs1 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor1).withStrokeStyle(ColorUtil.argb(0xFF, 0x40, 0x83, 0xB7)).build(); //Light Blue Color
final FastLineRenderableSeries rs2 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor2).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xA5, 0x00)).build(); //Light Pink Color
final FastLineRenderableSeries rs3 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor3).withStrokeStyle(ColorUtil.argb(0xFF, 0xE1, 0x32, 0x19)).build(); //Orange Red Color
final FastLineRenderableSeries rs4 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor4).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xFF, 0xFF)).build(); //White color
final FastLineRenderableSeries rs5 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor5).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xFF, 0x99)).build(); //Light Yellow color
final FastLineRenderableSeries rs6 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor6).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0x99, 0x33)).build(); //Light Orange color
Collections.addAll(plotSurface.getXAxes(), xAxis);
Collections.addAll(plotSurface.getYAxes(), yAxis);
Collections.addAll(plotSurface.getRenderableSeries(), rs1, rs2, rs3, rs4, rs5, rs6);
}
});
return frag;
}
//This class receives the UDP sensor data as messages to its handler
//Then it splices the data
//Adds the data to the IXySeries
//Then the UpdateSuspender updates the graph
//New data arrives approx every 50 ms (around 20x a second)
//Graphing slows when xAxis is increased to ~100,000
//X data is only counters...Only care about Y data
public class GraphDataSource extends Thread{
public void run(){
Looper.prepare();
//Get Data from UDP Data Class when its available
handler = new Handler(){
public void handleMessage(Message msg){
String sensorData = msg.getData().getString("data"); //Data receiveds
if(dataValid(sensorData)){
sensorData = sensorData.replaceAll("\s", "");
final String[] dataSplit = sensorData.split(","); //split the data at the commas
UpdateSuspender.using(plotSurface, new Runnable() { //This updater graphs the values
@Override
public void run() {
spliceDataAndAddData(dataSplit);
}
});
}
}
};
Looper.loop();
}
/**
*
* @param data string of the udp data
* @return true if the data isn't corrupted..aka the correct length
*/
private boolean dataValid(String data){
return ((data.length() == 1350));
}
/**
*
* @param dataSplit String[] of the entire data
* Adds the each sensor data to the IXySeries representing the data
*/
private void spliceDataAndAddData(String[] dataSplit){
addToSensorSeries(dataSplit, 1);
addToSensorSeries(dataSplit, 2);
addToSensorSeries(dataSplit, 3);
addToSensorSeries(dataSplit, 4);
addToSensorSeries(dataSplit, 5);
addToSensorSeries(dataSplit, 6);
}
/**
*
* @param dataSplit data to split into individual sensor array
* must contain only string representations of numbers
* @param sensorSeriesNumber which sensors to collect the data points of
* Adds the data to the corresponding IXySeries
*/
private void addToSensorSeries(String[] dataSplit, int sensorSeriesNumber){
sensorSeriesNumber -= 1; //Adds each value individually to the series
double xcounter = xCounters.get(sensorSeriesNumber);
int i = sensorSeriesNumber;
int dataSize = dataSplit.length - 1;
String num = "";
while(true){
if(i < 6){ //This is the base case...add the first set of data
num = dataSplit[i];
try {
if(xcounter > xBound){
xcounter = 0;
dataSeriesList.get(sensorSeriesNumber).clear();
}
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num)); //appends every number...
}catch (Exception e){
//Corrupt data
}
}else if((i) <= dataSize && i >= 6){ //Will start to get hit after the second time
num = dataSplit[i];
try {
if(xcounter > xBound){
xcounter = 0;
dataSeriesList.get(sensorSeriesNumber).clear();
}
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num));
}catch (Exception e){
//Corrupt data
}
}else{
break;
}
xcounter++;
i += 6;
}
xCounters.set(sensorSeriesNumber,xcounter);
}
}
我查看了您的代码,但不确定我们是否可以对此做些什么。您的示例包含 6 个 XyDataSeries,XRange 从 0 到 100000,这在屏幕上给出了 600 000 个点,这对于 HTC One 上的实时示例来说非常好。在 SciChart 性能演示中,您可以看到仅使用 3 个 XyDataSeries 实例,这允许在每个系列中绘制更多点
披露:我是 SciChart Android 团队的首席开发人员
但我认为您可以通过在代码中添加一些优化来获得额外的 FPS。实时图表中的主要问题在于更新图表的代码 - 它经常被调用,因此如果您在更新期间创建了一些对象并且不保存它那么这可能会导致问题,因为 Android 中的 GC(GC pass很慢,它可以在 GC 收集所有未使用的对象时暂停所有应用程序的线程)。所以我建议你接下来做:
- 我建议在您的应用程序中增加 heap size:如果您有效地使用内存,应用程序将拥有更多内存 - 执行的 GC 将更少。
- 尝试减少 boxing/unboxing 的数量并在频繁调用的代码中分配较少的对象(例如数据系列更新)。基本上,您需要忘记在更新数据系列的回调中创建任何对象。在您的代码中,我注意到 boxing/unboxing 出现的地方很少。此代码每秒调用一次,并且在循环中调用一些方法,因此 boxing/unboxing 的效果会显着影响应用程序的性能:
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num));
double xcounter = xCounters.get(sensorSeriesNumber);
xCounters.set(sensorSeriesNumber,xcounter);
我建议您使用接受 IValue 的 append override。当您非常频繁地附加大量数据时,使用接受 IValues 的附加可以避免不必要的 boxing/unboxing 原始类型。
- 此外,我建议使用 Float 或 Integer,除非您在创建 XyDataSeries 时确实需要 Double。这可能会减少一半的内存消耗(8 字节用于存储 double 与 4 字节用于存储 int/float),结果应用程序有更多可用内存,从而可以减少执行 GC 的频率。
希望对您有所帮助。
我正在使用 Scichart 为 android 编写一个实时绘图应用程序。我一直在使用
FastLineRenderableSeries 作为我的数据系列的包装器
但我想知道 还有哪些其他技术可以使用 Android SciChart 来最大化绘图速度?
特别是当我使用 IXyDataSeries 并将 x 轴大小从 10,000 点增加到 100,000 点时,我注意到性能下降。在我向我的 IXyDataSeries.
添加大约 90,000 个点之前,绘图的速度一直保持很快谢谢大家。我是 Whosebug 的新手……与其说是 CS 人员,不如说是 mechE。
这是我的 graphFragment class,它以字符串形式接收 UDP 传感器数据,将其拼接并添加到 IXyDataSeries。
public class GraphFragment extends Fragment {
//Various fields...
//UDP Settings
private UdpClient client;
private String hostname;
private int remotePort;
private int localPort;
//Use to communicate with UDPDataClass
private Handler handler;
private boolean listenerExists = false;
private int xBound = 100000; //**Graphing Slows if xBound is TOO large**
private int yBound = 5000;
private boolean applyBeenPressed = false;
private GraphDataSource dataSource; //Gets data from UDPDataClass
private SciChartSurface plotSurface; //Graphing Surface
protected final SciChartBuilder sciChartBuilder = SciChartBuilder.instance();
//Data Series containers
//Perhaps it would be better to use XyySeries here?
private final IXyDataSeries<Double, Double> dataSeriesSensor1 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor2 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor3 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor4 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor5 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private final IXyDataSeries<Double, Double> dataSeriesSensor6 = sciChartBuilder.newXyDataSeries(Double.class, Double.class).build();
private ArrayList<IXyDataSeries<Double,Double>> dataSeriesList = new ArrayList<>(Arrays.asList(dataSeriesSensor1,dataSeriesSensor2,dataSeriesSensor3,dataSeriesSensor4, dataSeriesSensor5, dataSeriesSensor6));
private ArrayList<Double> xCounters = new ArrayList<>(Arrays.asList(0.0,0.0,0.0,0.0,0.0,0.0));
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View frag = inflater.inflate(R.layout.graph_fragment, container, false);
plotSurface = (SciChartSurface) frag.findViewById(R.id.dynamic_plot);
dataSource = new GraphDataSource(); //Run the data handling on a separate thread
dataSource.start();
UpdateSuspender.using(plotSurface, new Runnable() {
@Override
public void run() {
final NumericAxis xAxis = sciChartBuilder.newNumericAxis().withVisibleRange(0,xBound).build();
final NumericAxis yAxis = sciChartBuilder.newNumericAxis().withVisibleRange(0,yBound).build();
//These are wrappers for the series we will add the data to...It contains the formatting
final FastLineRenderableSeries rs1 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor1).withStrokeStyle(ColorUtil.argb(0xFF, 0x40, 0x83, 0xB7)).build(); //Light Blue Color
final FastLineRenderableSeries rs2 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor2).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xA5, 0x00)).build(); //Light Pink Color
final FastLineRenderableSeries rs3 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor3).withStrokeStyle(ColorUtil.argb(0xFF, 0xE1, 0x32, 0x19)).build(); //Orange Red Color
final FastLineRenderableSeries rs4 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor4).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xFF, 0xFF)).build(); //White color
final FastLineRenderableSeries rs5 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor5).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0xFF, 0x99)).build(); //Light Yellow color
final FastLineRenderableSeries rs6 = sciChartBuilder.newLineSeries().withDataSeries(dataSeriesSensor6).withStrokeStyle(ColorUtil.argb(0xFF, 0xFF, 0x99, 0x33)).build(); //Light Orange color
Collections.addAll(plotSurface.getXAxes(), xAxis);
Collections.addAll(plotSurface.getYAxes(), yAxis);
Collections.addAll(plotSurface.getRenderableSeries(), rs1, rs2, rs3, rs4, rs5, rs6);
}
});
return frag;
}
//This class receives the UDP sensor data as messages to its handler
//Then it splices the data
//Adds the data to the IXySeries
//Then the UpdateSuspender updates the graph
//New data arrives approx every 50 ms (around 20x a second)
//Graphing slows when xAxis is increased to ~100,000
//X data is only counters...Only care about Y data
public class GraphDataSource extends Thread{
public void run(){
Looper.prepare();
//Get Data from UDP Data Class when its available
handler = new Handler(){
public void handleMessage(Message msg){
String sensorData = msg.getData().getString("data"); //Data receiveds
if(dataValid(sensorData)){
sensorData = sensorData.replaceAll("\s", "");
final String[] dataSplit = sensorData.split(","); //split the data at the commas
UpdateSuspender.using(plotSurface, new Runnable() { //This updater graphs the values
@Override
public void run() {
spliceDataAndAddData(dataSplit);
}
});
}
}
};
Looper.loop();
}
/**
*
* @param data string of the udp data
* @return true if the data isn't corrupted..aka the correct length
*/
private boolean dataValid(String data){
return ((data.length() == 1350));
}
/**
*
* @param dataSplit String[] of the entire data
* Adds the each sensor data to the IXySeries representing the data
*/
private void spliceDataAndAddData(String[] dataSplit){
addToSensorSeries(dataSplit, 1);
addToSensorSeries(dataSplit, 2);
addToSensorSeries(dataSplit, 3);
addToSensorSeries(dataSplit, 4);
addToSensorSeries(dataSplit, 5);
addToSensorSeries(dataSplit, 6);
}
/**
*
* @param dataSplit data to split into individual sensor array
* must contain only string representations of numbers
* @param sensorSeriesNumber which sensors to collect the data points of
* Adds the data to the corresponding IXySeries
*/
private void addToSensorSeries(String[] dataSplit, int sensorSeriesNumber){
sensorSeriesNumber -= 1; //Adds each value individually to the series
double xcounter = xCounters.get(sensorSeriesNumber);
int i = sensorSeriesNumber;
int dataSize = dataSplit.length - 1;
String num = "";
while(true){
if(i < 6){ //This is the base case...add the first set of data
num = dataSplit[i];
try {
if(xcounter > xBound){
xcounter = 0;
dataSeriesList.get(sensorSeriesNumber).clear();
}
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num)); //appends every number...
}catch (Exception e){
//Corrupt data
}
}else if((i) <= dataSize && i >= 6){ //Will start to get hit after the second time
num = dataSplit[i];
try {
if(xcounter > xBound){
xcounter = 0;
dataSeriesList.get(sensorSeriesNumber).clear();
}
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num));
}catch (Exception e){
//Corrupt data
}
}else{
break;
}
xcounter++;
i += 6;
}
xCounters.set(sensorSeriesNumber,xcounter);
}
}
我查看了您的代码,但不确定我们是否可以对此做些什么。您的示例包含 6 个 XyDataSeries,XRange 从 0 到 100000,这在屏幕上给出了 600 000 个点,这对于 HTC One 上的实时示例来说非常好。在 SciChart 性能演示中,您可以看到仅使用 3 个 XyDataSeries 实例,这允许在每个系列中绘制更多点
披露:我是 SciChart Android 团队的首席开发人员
但我认为您可以通过在代码中添加一些优化来获得额外的 FPS。实时图表中的主要问题在于更新图表的代码 - 它经常被调用,因此如果您在更新期间创建了一些对象并且不保存它那么这可能会导致问题,因为 Android 中的 GC(GC pass很慢,它可以在 GC 收集所有未使用的对象时暂停所有应用程序的线程)。所以我建议你接下来做:
- 我建议在您的应用程序中增加 heap size:如果您有效地使用内存,应用程序将拥有更多内存 - 执行的 GC 将更少。
- 尝试减少 boxing/unboxing 的数量并在频繁调用的代码中分配较少的对象(例如数据系列更新)。基本上,您需要忘记在更新数据系列的回调中创建任何对象。在您的代码中,我注意到 boxing/unboxing 出现的地方很少。此代码每秒调用一次,并且在循环中调用一些方法,因此 boxing/unboxing 的效果会显着影响应用程序的性能:
dataSeriesList.get(sensorSeriesNumber).append(xcounter, Double.parseDouble(num));
double xcounter = xCounters.get(sensorSeriesNumber);
xCounters.set(sensorSeriesNumber,xcounter);
我建议您使用接受 IValue 的 append override。当您非常频繁地附加大量数据时,使用接受 IValues 的附加可以避免不必要的 boxing/unboxing 原始类型。
- 此外,我建议使用 Float 或 Integer,除非您在创建 XyDataSeries 时确实需要 Double。这可能会减少一半的内存消耗(8 字节用于存储 double 与 4 字节用于存储 int/float),结果应用程序有更多可用内存,从而可以减少执行 GC 的频率。
希望对您有所帮助。