【发布时间】:2019-05-28 02:44:47
【问题描述】:
在 django 应用程序中,我试图将 Queryset 解析为元组 (t, x1, x2 . .. xn),然后转换成 json 对象,格式由此处的谷歌图表指定:https://developers.google.com/chart/interactive/docs/gallery/linechart
无如果没有记录来自特定传感器的给定时间戳的值,则使用值作为占位符
页面加载时间对于约 6500 行(约 3 秒,在本地运行)的 QuerySet 来说很重要
在服务器上明显更长
http://54.162.202.222/pulogger/simpleview/?device=test
分析表明 99.9% 的时间花在 _winapi.WaitForSingleObject(我无法解释)上,使用计时器进行手动分析表明服务器端的罪魁祸首是遍历 QuerySet 和组的 while 循环元组中的值(我的代码示例中的第 23 行)
结果如下:
基本获取(耗时 5 毫秒)
查询数据(耗时 0ms)
按传感器拆分数据(耗时 981 毫秒)
准备好列标签/类型(耗时 0 毫秒)
准备好的 json(耗时 27 毫秒)
创建上下文(耗时 0 毫秒)
为了完整起见,定时函数如下:
def print_elapsed_time(ref_datetime, description):
print('{} (took {}ms)'.format(description, floor((datetime.now()-ref_datetime).microseconds/1000)))
return datetime.now()
执行处理和生成视图的代码如下:
def simpleview(request):
time_marker = datetime.now()
device_name = request.GET['device']
device = Datalogger.objects.get(device_name=device_name)
sensors = Sensor.objects.filter(datalogger=device).order_by('pk')
sensor_count = len(sensors) # should be no worse than count() since already-evaluated and cached. todo: confirm
#assign each sensor an index for the tuples (zero is used for time/x-axis)
sensor_indices = {}
for idx, sensor in enumerate(sensors, start=1):
sensor_indices.update({sensor.sensor_name:idx})
time_marker = print_elapsed_time(time_marker, 'basic gets')
# process data into timestamp-grouped tuples accessible by sensor-index ([0] is timestamp)
raw_data = SensorDatum.objects.filter(sensor__datalogger__device_name=device_name).order_by('timestamp', 'sensor')
data = []
data_idx = 0
time_marker = print_elapsed_time(time_marker, 'queried data')
while data_idx < len(raw_data):
row_list = [raw_data[data_idx].timestamp]
row_list.extend([None]*sensor_count)
row_idx = 1
while data_idx < len(raw_data) and raw_data[data_idx].timestamp == row_list[0]:
row_idx = sensor_indices.get(raw_data[data_idx].sensor.sensor_name)
row_list[row_idx] = raw_data[data_idx].value
data_idx += 1
data.append(tuple(row_list))
time_marker = print_elapsed_time(time_marker, 'split data by sensor')
column_labels = ['Time']
column_types = ["datetime"]
for sensor in sensors:
column_labels.append(sensor.sensor_name)
column_types.append("number")
time_marker = print_elapsed_time(time_marker, 'prepared column labels/types')
gchart_json = prepare_data_for_gchart(column_labels, column_types, data)
time_marker = print_elapsed_time(time_marker, 'prepared json')
context = {
'device': device_name,
'sensor_count': sensor_count,
'sensor_indices': sensor_indices,
'gchart_json': gchart_json,
}
time_marker = print_elapsed_time(time_marker, 'created context')
return render(request, 'pulogger/simpleTimeSeriesView.html', context)
我是 python 新手,所以我认为我在某处使用过的操作/集合选择不佳。除非我是盲人,否则它应该在 O(n) 内运行。
显然这不是全部问题,因为它只占表面加载时间的一部分,但我认为这是一个很好的起点。
【问题讨论】:
-
在尝试了一些进一步的测试之后,问题似乎是在
row_idx = sensor_indices.get(raw_data[data_idx].sensor.sensor_name)中对raw_data[data_idx].sensor.sensor_name的评估所以这是我处理django 对象的方式的问题。我的理解是,在每次评估中,它都会通过索引(快速)跳转到相关的 QuerySet 元素(SensorDatum),检索关联的 Sensor 对象(可能很慢),然后获取其 sensor_name(快速)。最好的解决方案是去规范化,在插入行时在 SensorDatum 的字段中复制 sensor_name? -
如果您事先将查询集转换为列表,这可能是最好的。喜欢
raw_data = list(SensorDatum.objects.filter(sensor__datalogger__device_name=device_name).order_by('timestamp', 'sensor')) -
@ruddra 如何与外键字段交互?对应的列表元素会变成某种对象还是只是与其主键对应的 int?
-
不,您将按原样使用它。但不是多次评估查询集,而是评估一次。希望它会提高性能,但我不确定。更多信息可以在这里找到:docs.djangoproject.com/en/2.1/ref/models/querysets/…
-
使用
all(),filter()不会评估查询集。调用len()、list、get()、切片查询集等。
标签: python django python-3.x performance django-models